diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 55d98ac0ede2f808e1a8984de6e58a2421c7edd1..565f81db46c4ffbee805bbb1f4e34419d2d6b049 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -1157,7 +1157,7 @@ this, :mod:`loopy` will complain that global barrier needs to be inserted: >>> cgr = lp.generate_code_v2(knl) Traceback (most recent call last): ... - loopy.diagnostic.MissingBarrierError: Dependency 'rotate depends on maketmp' (for variable 'arr') requires synchronization by a global barrier (add a 'no_sync_with' instruction option to state that no synchronization is needed) + loopy.diagnostic.MissingBarrierError: rotate_v1: Dependency 'rotate depends on maketmp' (for variable 'arr') requires synchronization by a global barrier (add a 'no_sync_with' instruction option to state that no synchronization is needed) The syntax for a inserting a global barrier instruction is ``... gbarrier``. :mod:`loopy` also supports manually inserting local @@ -1554,7 +1554,7 @@ information provided. Now we will count the operations: >>> op_map = lp.get_op_map(knl, subgroup_size=32) >>> print(lp.stringify_stats_mapping(op_map)) - Op(np:dtype('float32'), add, subgroup) : ... + Op(np:dtype('float32'), add, subgroup, loopy_kernel) : ... Each line of output will look roughly like:: diff --git a/examples/fortran/ipython-integration-demo.ipynb b/examples/fortran/ipython-integration-demo.ipynb index 7a5c8257bf80fdfcc3d3b978a7dca2d401c48271..1b0a9df8d18da1947171eadf744cb3db2ea312da 100644 --- a/examples/fortran/ipython-integration-demo.ipynb +++ b/examples/fortran/ipython-integration-demo.ipynb @@ -62,9 +62,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "split_amount = 128" @@ -91,7 +89,7 @@ "\n", "!$loopy begin\n", "!\n", - "! tr_fill, = lp.parse_fortran(SOURCE)\n", + "! tr_fill = lp.parse_fortran(SOURCE)\n", "! tr_fill = lp.split_iname(tr_fill, \"i\", split_amount,\n", "! outer_tag=\"g.0\", inner_tag=\"l.0\")\n", "! RESULT = [tr_fill]\n", @@ -107,15 +105,6 @@ "source": [ "print(tr_fill)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { @@ -134,7 +123,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/examples/fortran/matmul.floopy b/examples/fortran/matmul.floopy index 4b35522043bfc32b71c0a063c3efc3b4403a26f2..a8377beddb912a2d6b1d9255694336313089a0f9 100644 --- a/examples/fortran/matmul.floopy +++ b/examples/fortran/matmul.floopy @@ -13,7 +13,7 @@ subroutine dgemm(m,n,l,alpha,a,b,c) end subroutine !$loopy begin -! dgemm, = lp.parse_fortran(SOURCE, FILENAME) +! dgemm = lp.parse_fortran(SOURCE, FILENAME) ! dgemm = lp.split_iname(dgemm, "i", 16, ! outer_tag="g.0", inner_tag="l.1") ! dgemm = lp.split_iname(dgemm, "j", 8, @@ -24,5 +24,5 @@ end subroutine ! dgemm = lp.extract_subst(dgemm, "b_acc", "b[i1,i2]", parameters="i1, i2") ! dgemm = lp.precompute(dgemm, "a_acc", "k_inner,i_inner", default_tag="l.auto") ! dgemm = lp.precompute(dgemm, "b_acc", "j_inner,k_inner", default_tag="l.auto") -! RESULT = [dgemm] +! RESULT = dgemm !$loopy end diff --git a/examples/fortran/sparse.floopy b/examples/fortran/sparse.floopy index 18542e6b0403a7ab475b3e357f18489847367c3d..2b156bdd709e8f4258492d258adb888ad16fbccd 100644 --- a/examples/fortran/sparse.floopy +++ b/examples/fortran/sparse.floopy @@ -23,11 +23,11 @@ subroutine sparse(rowstarts, colindices, values, m, n, nvals, x, y) end !$loopy begin -! sparse, = lp.parse_fortran(SOURCE, FILENAME) +! sparse = lp.parse_fortran(SOURCE, FILENAME) ! sparse = lp.split_iname(sparse, "i", 128) ! sparse = lp.tag_inames(sparse, {"i_outer": "g.0"}) ! sparse = lp.tag_inames(sparse, {"i_inner": "l.0"}) ! sparse = lp.split_iname(sparse, "j", 4) ! sparse = lp.tag_inames(sparse, {"j_inner": "unr"}) -! RESULT = [sparse] +! RESULT = sparse !$loopy end diff --git a/examples/fortran/tagging.floopy b/examples/fortran/tagging.floopy index 87aacba68ae2fc6f3b7052325fcd2378e9880e47..c7ebb75667142a8bb470b32f1d92177e135db9b2 100644 --- a/examples/fortran/tagging.floopy +++ b/examples/fortran/tagging.floopy @@ -23,13 +23,13 @@ end ! "factor 4.0", ! "real_type real*8", ! ]) -! fill, = lp.parse_fortran(SOURCE, FILENAME) +! fill = lp.parse_fortran(SOURCE, FILENAME) ! fill = lp.add_barrier(fill, "tag:init", "tag:mult", "gb1") ! fill = lp.split_iname(fill, "i", 128, ! outer_tag="g.0", inner_tag="l.0") ! fill = lp.split_iname(fill, "i_1", 128, ! outer_tag="g.0", inner_tag="l.0") -! RESULT = [fill] +! RESULT = fill ! !$loopy end diff --git a/examples/fortran/volumeKernel.floopy b/examples/fortran/volumeKernel.floopy index c5784b63492063bfd2a9604c42dbf65b2ecb86bf..211c38049076cbe065ce847f948d724c293a032c 100644 --- a/examples/fortran/volumeKernel.floopy +++ b/examples/fortran/volumeKernel.floopy @@ -67,7 +67,7 @@ end subroutine volumeKernel !$loopy begin ! -! volumeKernel, = lp.parse_fortran(SOURCE, FILENAME) +! volumeKernel = lp.parse_fortran(SOURCE, FILENAME) ! volumeKernel = lp.split_iname(volumeKernel, ! "e", 32, outer_tag="g.1", inner_tag="g.0") ! volumeKernel = lp.fix_parameters(volumeKernel, @@ -76,6 +76,6 @@ end subroutine volumeKernel ! i="l.0", j="l.1", k="l.2", ! i_1="l.0", j_1="l.1", k_1="l.2" ! )) -! RESULT = [volumeKernel] +! RESULT = volumeKernel ! !$loopy end diff --git a/loopy/__init__.py b/loopy/__init__.py index 78bfd70a09a466cda778570d97aea1fd3d32ca4b..819eccbd3a0feb303a528098b70cb8d3d411f079 100644 --- a/loopy/__init__.py +++ b/loopy/__init__.py @@ -131,10 +131,10 @@ from loopy.preprocess import (preprocess_kernel, realize_reduction, preprocess_program) from loopy.schedule import ( generate_loop_schedules, get_one_scheduled_kernel, get_one_linearized_kernel) -from loopy.statistics import (ToCountMap, CountGranularity, +from loopy.statistics import (ToCountMap, ToCountPolynomialMap, CountGranularity, stringify_stats_mapping, Op, MemAccess, get_op_map, get_mem_access_map, get_synchronization_map, gather_access_footprints, - gather_access_footprint_bytes) + gather_access_footprint_bytes, Sync) from loopy.codegen import ( PreambleInfo, generate_code, generate_code_v2, generate_body) @@ -271,10 +271,11 @@ __all__ = [ "PreambleInfo", "generate_code", "generate_code_v2", "generate_body", - "ToCountMap", "CountGranularity", "stringify_stats_mapping", "Op", - "MemAccess", "get_op_map", "get_mem_access_map", - "get_synchronization_map", "gather_access_footprints", - "gather_access_footprint_bytes", + "ToCountMap", "ToCountPolynomialMap", "CountGranularity", + "stringify_stats_mapping", "Op", "MemAccess", "get_op_map", + "get_mem_access_map", "get_synchronization_map", + "gather_access_footprints", "gather_access_footprint_bytes", + "Sync", "CompiledKernel", diff --git a/loopy/auto_test.py b/loopy/auto_test.py index a079795bd988a91c65738305660a51d0ee6d55c8..ebc07e1fce26f8d0f405ca5e699e32480e21fa4d 100644 --- a/loopy/auto_test.py +++ b/loopy/auto_test.py @@ -120,7 +120,7 @@ def make_ref_args(program, impl_arg_info, queue, parameters): shape = evaluate_shape(arg.unvec_shape, parameters) dtype = kernel_arg.dtype - is_output = kernel_arg.is_output_only + is_output = kernel_arg.is_output if arg.arg_class is ImageArg: storage_array = ary = cl_array.empty( diff --git a/loopy/frontend/fortran/__init__.py b/loopy/frontend/fortran/__init__.py index 3516ca29a880af18936c558c2a6a457af2e3236c..9b63c10f8422d0a17c295e1ef9a4609f5db90e2b 100644 --- a/loopy/frontend/fortran/__init__.py +++ b/loopy/frontend/fortran/__init__.py @@ -241,10 +241,64 @@ def parse_transformed_fortran(source, free_form=True, strict=True, return proc_dict["RESULT"] +def _add_assignees_to_calls(knl, all_kernels): + """ + Returns a copy of *knl* coming from the fortran parser adjusted to the + loopy specification that written variables of a call must appear in the + assignee. + + :param knl: An instance of :class:`loopy.LoopKernel`, which have incorrect + calls to the kernels in *all_kernels* by stuffing both the input and + output arguments into parameters. + + :param all_kernels: An instance of :class:`list` of loopy kernels which + may be called by *kernel*. + """ + new_insns = [] + subroutine_dict = dict((kernel.name, kernel) for kernel in all_kernels) + from loopy.kernel.instruction import (Assignment, CallInstruction, + CInstruction, _DataObliviousInstruction, + modify_assignee_for_array_call) + from pymbolic.primitives import Call, Variable + + for insn in knl.instructions: + if isinstance(insn, CallInstruction): + if isinstance(insn.expression, Call) and ( + insn.expression.function.name in subroutine_dict): + assignees = [] + new_params = [] + subroutine = subroutine_dict[insn.expression.function.name] + for par, arg in zip(insn.expression.parameters, subroutine.args): + if arg.name in subroutine.get_written_variables(): + par = modify_assignee_for_array_call(par) + assignees.append(par) + if arg.name in subroutine.get_read_variables(): + new_params.append(par) + if arg.name not in (subroutine.get_written_variables() | + subroutine.get_read_variables()): + new_params.append(par) + + new_insns.append( + insn.copy( + assignees=tuple(assignees), + expression=Variable( + insn.expression.function.name)(*new_params))) + else: + new_insns.append(insn) + pass + elif isinstance(insn, (Assignment, CInstruction, + _DataObliviousInstruction)): + new_insns.append(insn) + else: + raise NotImplementedError(type(insn).__name__) + + return knl.copy(instructions=new_insns) + + def parse_fortran(source, filename="", free_form=None, strict=None, seq_dependencies=None, auto_dependencies=None, target=None): """ - :returns: a :class:`loopy.Program` + :returns: A :class:`loopy.Program`. """ parse_plog = ProcessLogger(logger, "parsing fortran file '%s'" % filename) @@ -286,6 +340,8 @@ def parse_fortran(source, filename="", free_form=None, strict=None, kernels = f2loopy.make_kernels(seq_dependencies=seq_dependencies) + kernels = [_add_assignees_to_calls(knl, kernels) for knl in kernels] + from loopy.kernel.tools import identify_root_kernel from loopy.program import make_program from loopy.transform.callable import register_callable_kernel diff --git a/loopy/frontend/fortran/translator.py b/loopy/frontend/fortran/translator.py index 260d1b8ecf9cdb332aa651795fb9526da681e700..39c2c62d97b23cd44f64ab59920e4336991a47b5 100644 --- a/loopy/frontend/fortran/translator.py +++ b/loopy/frontend/fortran/translator.py @@ -763,7 +763,6 @@ class F2LoopyTranslator(FTreeWalkerBase): arg_name, dtype=sub.get_type(arg_name), shape=sub.get_loopy_shape(arg_name), - is_output_only=False, )) else: kernel_data.append( diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index 1f896bb973c2ebc17294ffd5031998c6af962ca6..5582b0c633ff47f218e0ccaaf21a99e8946f5451 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -37,6 +37,7 @@ from loopy.kernel.data import ( SubstitutionRule, AddressSpace, ValueArg) from loopy.kernel.instruction import (CInstruction, _DataObliviousInstruction, CallInstruction) +from loopy.program import iterate_over_kernels_if_given_program from loopy.diagnostic import LoopyError, warn_with_kernel import islpy as isl from islpy import dim_type @@ -1753,6 +1754,7 @@ def add_inferred_inames(knl): # {{{ apply single-writer heuristic +@iterate_over_kernels_if_given_program def apply_single_writer_depencency_heuristic(kernel, warn_if_used=True): logger.debug("%s: default deps" % kernel.name) @@ -1886,9 +1888,18 @@ class SliceToInameReplacer(IdentityMapper): self.var_name_gen = var_name_gen self.knl = knl + # caching to map equivalent slices to equivalent SubArrayRefs + self.cache = {} + self.subarray_ref_bounds = [] + def clear_cache(self): + self.cache = {} + def map_subscript(self, expr): + if expr in self.cache: + return self.cache[expr] + subscript_iname_bounds = {} self.subarray_ref_bounds.append(subscript_iname_bounds) @@ -1917,11 +1928,15 @@ class SliceToInameReplacer(IdentityMapper): new_index.append(index) if swept_inames: - return SubArrayRef(tuple(swept_inames), Subscript( + result = SubArrayRef(tuple(swept_inames), Subscript( self.rec(expr.aggregate), self.rec(tuple(new_index)))) else: - return IdentityMapper.map_subscript(self, expr) + result = IdentityMapper.map_subscript(self, expr) + + self.cache[expr] = result + + return result def map_call(self, expr): def _convert_array_to_slices(arg): @@ -2012,6 +2027,8 @@ def realize_slices_array_inputs_as_sub_array_refs(kernel): raise NotImplementedError("Unknown type of instruction -- %s" % type(insn)) + slice_replacer.clear_cache() + return kernel.copy( domains=( kernel.domains @@ -2175,56 +2192,55 @@ def make_kernel(domains, instructions, kernel_data=["..."], **kwargs): # {{{ handle kernel language version - if not is_callee_kernel: - from loopy.version import LANGUAGE_VERSION_SYMBOLS + from loopy.version import LANGUAGE_VERSION_SYMBOLS - version_to_symbol = dict( - (getattr(loopy.version, lvs), lvs) - for lvs in LANGUAGE_VERSION_SYMBOLS) + version_to_symbol = dict( + (getattr(loopy.version, lvs), lvs) + for lvs in LANGUAGE_VERSION_SYMBOLS) - lang_version = kwargs.pop("lang_version", None) - if lang_version is None: - # {{{ peek into caller's module to look for LOOPY_KERNEL_LANGUAGE_VERSION + lang_version = kwargs.pop("lang_version", None) + if lang_version is None: + # {{{ peek into caller's module to look for LOOPY_KERNEL_LANGUAGE_VERSION - # This *is* gross. But it seems like the right thing interface-wise. - import inspect - caller_globals = inspect.currentframe().f_back.f_globals + # This *is* gross. But it seems like the right thing interface-wise. + import inspect + caller_globals = inspect.currentframe().f_back.f_globals - for ver_sym in LANGUAGE_VERSION_SYMBOLS: - try: - lang_version = caller_globals[ver_sym] - break - except KeyError: - pass + for ver_sym in LANGUAGE_VERSION_SYMBOLS: + try: + lang_version = caller_globals[ver_sym] + break + except KeyError: + pass - # }}} + # }}} - if lang_version is None: - from warnings import warn - from loopy.diagnostic import LoopyWarning - from loopy.version import ( - MOST_RECENT_LANGUAGE_VERSION, - FALLBACK_LANGUAGE_VERSION) - warn("'lang_version' was not passed to make_kernel(). " - "To avoid this warning, pass " - "lang_version={ver} in this invocation. " - "(Or say 'from loopy.version import " - "{sym_ver}' in " - "the global scope of the calling frame.)" - .format( - ver=MOST_RECENT_LANGUAGE_VERSION, - sym_ver=version_to_symbol[MOST_RECENT_LANGUAGE_VERSION] - ), - LoopyWarning, stacklevel=2) - - lang_version = FALLBACK_LANGUAGE_VERSION - - if lang_version not in version_to_symbol: - raise LoopyError("Language version '%s' is not known." % (lang_version,)) - if lang_version >= (2018, 1): - options = options.copy(enforce_variable_access_ordered=True) - if lang_version >= (2018, 2): - options = options.copy(ignore_boostable_into=True) + if lang_version is None: + from warnings import warn + from loopy.diagnostic import LoopyWarning + from loopy.version import ( + MOST_RECENT_LANGUAGE_VERSION, + FALLBACK_LANGUAGE_VERSION) + warn("'lang_version' was not passed to make_kernel(). " + "To avoid this warning, pass " + "lang_version={ver} in this invocation. " + "(Or say 'from loopy.version import " + "{sym_ver}' in " + "the global scope of the calling frame.)" + .format( + ver=MOST_RECENT_LANGUAGE_VERSION, + sym_ver=version_to_symbol[MOST_RECENT_LANGUAGE_VERSION] + ), + LoopyWarning, stacklevel=2) + + lang_version = FALLBACK_LANGUAGE_VERSION + + if lang_version not in version_to_symbol: + raise LoopyError("Language version '%s' is not known." % (lang_version,)) + if lang_version >= (2018, 1): + options = options.copy(enforce_variable_access_ordered=True) + if lang_version >= (2018, 2): + options = options.copy(ignore_boostable_into=True) # }}} @@ -2366,8 +2382,8 @@ def make_kernel(domains, instructions, kernel_data=["..."], **kwargs): check_for_duplicate_names(knl) check_written_variable_names(knl) - from loopy.kernel.tools import infer_args_are_output_only - knl = infer_args_are_output_only(knl) + from loopy.kernel.tools import infer_args_are_input_output + knl = infer_args_are_input_output(knl) from loopy.preprocess import prepare_for_caching knl = prepare_for_caching(knl) @@ -2382,11 +2398,6 @@ def make_kernel(domains, instructions, kernel_data=["..."], **kwargs): def make_function(*args, **kwargs): - lang_version = kwargs.pop('lang_version', None) - if lang_version: - raise LoopyError("lang_version should be set for program, not " - "functions.") - kwargs['is_callee_kernel'] = True return make_kernel(*args, **kwargs) diff --git a/loopy/kernel/data.py b/loopy/kernel/data.py index 434ee3884b6ad42cf7c5d0a690850c203e0d17ef..6c0fa0a303d22fa931fe797ff3653d2819d4aa8d 100644 --- a/loopy/kernel/data.py +++ b/loopy/kernel/data.py @@ -338,7 +338,8 @@ class KernelArgument(ImmutableRecord): dtype = None kwargs["dtype"] = dtype - kwargs["is_output_only"] = kwargs.pop("is_output_only", None) + kwargs["is_output"] = kwargs.pop("is_output", None) + kwargs["is_input"] = kwargs.pop("is_input", None) ImmutableRecord.__init__(self, **kwargs) @@ -351,20 +352,37 @@ class ArrayArg(ArrayBase, KernelArgument): An attribute of :class:`AddressSpace` defining the address space in which the array resides. - .. attribute:: is_output_only + .. attribute:: is_output - An instance of :class:`bool`. If set to *True*, recorded to be - returned from the kernel. + An instance of :class:`bool`. If set to *True*, the argument is used + to return information to the caller. If set to *False*, then the + callee should not write the array during execution. + + .. attribute:: is_input + + An instance of :class:`bool`. If set to *True*, expected to be + provided by the caller. If *False* then the callee should not depend + on the state of the array on entry to a function. """) allowed_extra_kwargs = [ "address_space", - "is_output_only"] + "is_output", + "is_input"] def __init__(self, *args, **kwargs): if "address_space" not in kwargs: raise TypeError("'address_space' must be specified") - kwargs["is_output_only"] = kwargs.pop("is_output_only", None) + + is_output_only = kwargs.pop("is_output_only", None) + if is_output_only is not None: + warn("'is_output_only' is deprecated. Use 'is_output', 'is_input'" + " instead.", DeprecationWarning, stacklevel=2) + kwargs["is_output"] = is_output_only + kwargs["is_input"] = not is_output_only + else: + kwargs["is_output"] = kwargs.pop("is_output", None) + kwargs["is_input"] = kwargs.pop("is_input", None) super(ArrayArg, self).__init__(*args, **kwargs) @@ -392,7 +410,8 @@ class ArrayArg(ArrayBase, KernelArgument): """ super(ArrayArg, self).update_persistent_hash(key_hash, key_builder) key_builder.rec(key_hash, self.address_space) - key_builder.rec(key_hash, self.is_output_only) + key_builder.rec(key_hash, self.is_output) + key_builder.rec(key_hash, self.is_input) # Making this a function prevents incorrect use in isinstance. @@ -413,7 +432,8 @@ class ConstantArg(ArrayBase, KernelArgument): max_target_axes = 1 # Constant Arg cannot be an output - is_output_only = False + is_output = False + is_input = True def get_arg_decl(self, ast_builder, name_suffix, shape, dtype, is_written): return ast_builder.get_constant_arg_decl(self.name + name_suffix, shape, @@ -436,13 +456,14 @@ class ImageArg(ArrayBase, KernelArgument): class ValueArg(KernelArgument): def __init__(self, name, dtype=None, approximately=1000, target=None, - is_output_only=False): + is_output=False, is_input=True): KernelArgument.__init__(self, name=name, dtype=dtype, approximately=approximately, target=target, - is_output_only=is_output_only) + is_output=is_output, + is_input=is_input) def __str__(self): import loopy as lp diff --git a/loopy/kernel/function_interface.py b/loopy/kernel/function_interface.py index 0cb610074116efcc0047e6301472b6c7782f6954..9c520ce96b8f9b13498d0171b22215a115e4c63a 100644 --- a/loopy/kernel/function_interface.py +++ b/loopy/kernel/function_interface.py @@ -226,16 +226,13 @@ def get_kw_pos_association(kernel): write_count = -1 for arg in kernel.args: - if arg.name in kernel.get_written_variables(): + if arg.is_output: kw_to_pos[arg.name] = write_count pos_to_kw[write_count] = arg.name write_count -= 1 - if arg.name in kernel.get_read_variables(): - kw_to_pos[arg.name] = read_count - pos_to_kw[read_count] = arg.name - read_count += 1 - if not (arg.name in kernel.get_read_variables() or arg.name in - kernel.get_written_variables()): + if arg.is_input: + # if an argument is both input and output then kw_to_pos is + # overwritten with its expected position in the parameters kw_to_pos[arg.name] = read_count pos_to_kw[read_count] = arg.name read_count += 1 @@ -862,10 +859,12 @@ class CallableKernel(InKernelCallable): # insert the assignees at the required positions assignee_write_count = -1 for i, arg in enumerate(self.subkernel.args): - if arg.is_output_only: - assignee = assignees[-assignee_write_count-1] - parameters.insert(i, assignee) - par_dtypes.insert(i, self.arg_id_to_dtype[assignee_write_count]) + if arg.is_output: + if not arg.is_input: + assignee = assignees[-assignee_write_count-1] + parameters.insert(i, assignee) + par_dtypes.insert(i, self.arg_id_to_dtype[assignee_write_count]) + assignee_write_count -= 1 # no type casting in array calls diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 3d2f16bb735df860b72f67dd09067f525ba1a19c..c5599863c9260086015e53efc413faf667a80738 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -544,7 +544,8 @@ def _get_assignee_subscript_deps(expr): elif isinstance(expr, LinearSubscript): return get_dependencies(expr.index) elif isinstance(expr, SubArrayRef): - return get_dependencies(expr.get_begin_subscript().index) + return get_dependencies(expr.subscript.index) - ( + frozenset(iname.name for iname in expr.swept_inames)) else: raise RuntimeError("invalid lvalue '%s'" % expr) @@ -1209,7 +1210,7 @@ def is_array_call(assignees, expression): return False -def modify_assignee_assignee_for_array_call(assignee): +def modify_assignee_for_array_call(assignee): """ Converts the assignee subscript or variable as a SubArrayRef. """ @@ -1259,7 +1260,7 @@ def make_assignment(assignees, expression, temp_var_types=None, **kwargs): # assignee as an instance of SubArrayRef. If not given as a # SubArrayRef return CallInstruction( - assignees=tuple(modify_assignee_assignee_for_array_call( + assignees=tuple(modify_assignee_for_array_call( assignee) for assignee in assignees), expression=expression, temp_var_types=temp_var_types, diff --git a/loopy/kernel/tools.py b/loopy/kernel/tools.py index b2c054a37367a74e9a60f9b8a4fbc6cf82b14a9c..ead996445844e1cc3d09b5a7683b40201dcb6d34 100644 --- a/loopy/kernel/tools.py +++ b/loopy/kernel/tools.py @@ -1922,34 +1922,55 @@ def get_direct_callee_kernels(kernel, callables_table, insn_ids=None,): # {{{ direction helper tools -def infer_args_are_output_only(kernel): +def infer_args_are_input_output(kernel): """ - Returns a copy of *kernel* with the attribute ``is_output_only`` set. + Returns a copy of *kernel* with the attributes ``is_input`` and + ``is_output`` of the arguments set. .. note:: - If the attribute ``is_output_only`` is not supplied from an user, then - infers it as an output argument if it is written at some point in the - kernel. + If the :attr:`~loopy.ArrayArg.is_output` is not supplied from a user, + then the array is inferred as an output argument if it is written at + some point in the kernel. + + If the :attr:`~loopy.ArrayArg.is_input` is not supplied from a user, + then the array is inferred as an input argument if it is either read at + some point in the kernel or it is neither read nor written. """ from loopy.kernel.data import ArrayArg, ValueArg, ConstantArg, ImageArg new_args = [] for arg in kernel.args: if isinstance(arg, ArrayArg): - if arg.is_output_only is not None: - assert isinstance(arg.is_output_only, bool) - new_args.append(arg) + if arg.is_output is not None: + assert isinstance(arg.is_output, bool) else: if arg.name in kernel.get_written_variables(): - new_args.append(arg.copy(is_output_only=True)) + arg = arg.copy(is_output=True) + else: + arg = arg.copy(is_output=False) + + if arg.is_input is not None: + assert isinstance(arg.is_input, bool) + else: + if arg.name in kernel.get_read_variables() or ( + (arg.name not in kernel.get_read_variables()) and ( + arg.name not in kernel.get_written_variables())): + arg = arg.copy(is_input=True) else: - new_args.append(arg.copy(is_output_only=False)) + arg = arg.copy(is_input=False) elif isinstance(arg, (ConstantArg, ImageArg, ValueArg)): - new_args.append(arg) + pass else: raise NotImplementedError("Unkonwn argument type %s." % type(arg)) + if not (arg.is_input or arg.is_output): + raise LoopyError("Kernel argument must be either input or output." + " '{}' in '{}' does not follow it.".format(arg.name, + kernel.name)) + + new_args.append(arg) + return kernel.copy(args=new_args) # }}} diff --git a/loopy/program.py b/loopy/program.py index 1fb691531768bcf202327e485daa03f842356782..f862144037aee21e113ad2ccd43b79ceefd39b55 100644 --- a/loopy/program.py +++ b/loopy/program.py @@ -403,6 +403,11 @@ class Program(ImmutableRecord): strify_callable(clbl) for name, clbl in self.callables_table.items()) + def __setstate__(self, state_obj): + super(Program, self).__setstate__(state_obj) + + self._program_executor_cache = {} + # }}} diff --git a/loopy/symbolic.py b/loopy/symbolic.py index b21b9937c680529a39d825d96822f780cf0f7f99..b8341bcd15b76eeb1705b8ca26d69237a6b97c0d 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -198,7 +198,9 @@ class CombineMapper(CombineMapperBase): return self.rec(expr.expr, *args, **kwargs) def map_sub_array_ref(self, expr): - return self.rec(expr.get_begin_subscript()) + return self.combine(( + self.rec(expr.subscript), + self.combine(tuple(self.rec(idx) for idx in expr.swept_inames)))) map_linear_subscript = CombineMapperBase.map_subscript @@ -360,9 +362,9 @@ class DependencyMapper(DependencyMapperBase): def map_loopy_function_identifier(self, expr, *args, **kwargs): return set() - def map_sub_array_ref(self, expr, *args): - deps = self.rec(expr.subscript, *args) - return deps - set(iname for iname in expr.swept_inames) + def map_sub_array_ref(self, expr, *args, **kwargs): + deps = self.rec(expr.subscript, *args, **kwargs) + return deps - set(expr.swept_inames) map_linear_subscript = DependencyMapperBase.map_subscript @@ -726,7 +728,7 @@ class RuleArgument(LoopyExpressionBase): mapper_method = intern("map_rule_argument") -class ResolvedFunction(p.Expression): +class ResolvedFunction(LoopyExpressionBase): """ A function invocation whose definition is known in a :mod:`loopy` kernel. Each instance of :class:`loopy.symbolic.ResolvedFunction` in an expression @@ -765,8 +767,8 @@ class ResolvedFunction(p.Expression): def __getinitargs__(self): return (self.function, ) - def stringifier(self): - return StringifyMapper + def make_stringifier(self, originating_stringifier=None): + return StringifyMapper() mapper_method = intern("map_resolved_function") @@ -814,7 +816,28 @@ class SweptInameStrideCollector(CoefficientCollectorBase): return super(SweptInameStrideCollector, self).map_algebraic_leaf(expr) -class SubArrayRef(p.Expression): +def get_start_subscript_from_sar(sar, kernel): + """ + Returns an instance of :class:`pymbolic.primitives.Subscript`, the + beginning subscript of the array swept by the *SubArrayRef*. + + **Example:** Consider ``[i, k]: a[i, j, k, l]``. The beginning + subscript would be ``a[0, j, 0, l]`` + """ + + def _get_lower_bound(iname): + pwaff = kernel.get_iname_bounds(iname).lower_bound_pw_aff + return int(pw_aff_to_expr(pwaff)) + + swept_inames_to_zeros = dict( + (swept_iname.name, _get_lower_bound(swept_iname.name)) for + swept_iname in sar.swept_inames) + + return EvaluatorWithDeficientContext(swept_inames_to_zeros)( + sar.subscript) + + +class SubArrayRef(LoopyExpressionBase): """ An algebraic expression to map an affine memory layout pattern (known as sub-arary) as consecutive elements of the sweeping axes which are defined @@ -852,21 +875,6 @@ class SubArrayRef(p.Expression): self.swept_inames = swept_inames self.subscript = subscript - def get_begin_subscript(self): - """ - Returns an instance of :class:`pymbolic.primitives.Subscript`, the - beginning subscript of the array swept by the *SubArrayRef*. - - **Example:** Consider ``[i, k]: a[i, j, k, l]``. The beginning - subscript would be ``a[0, j, 0, l]`` - """ - # TODO: Set the zero to the minimum value of the iname. - swept_inames_to_zeros = dict( - (swept_iname.name, 0) for swept_iname in self.swept_inames) - - return EvaluatorWithDeficientContext(swept_inames_to_zeros)( - self.subscript) - def __getinitargs__(self): return (self.swept_inames, self.subscript) @@ -878,8 +886,8 @@ class SubArrayRef(p.Expression): and other.subscript == self.subscript and other.swept_inames == self.swept_inames) - def stringifier(self): - return StringifyMapper + def make_stringifier(self, originating_stringifier=None): + return StringifyMapper() mapper_method = intern("map_sub_array_ref") diff --git a/loopy/target/c/codegen/expression.py b/loopy/target/c/codegen/expression.py index c970901b1ce9161c713d83296f4ca7f05b8f433c..b0bc187ebe71c2e9751ce95abe0050b5c06d6f26 100644 --- a/loopy/target/c/codegen/expression.py +++ b/loopy/target/c/codegen/expression.py @@ -167,7 +167,8 @@ class ExpressionToCExpressionMapper(IdentityMapper): return var(expr.name) def map_sub_array_ref(self, expr, type_context): - return var("&")(self.rec(expr.get_begin_subscript(), + from loopy.symbolic import get_start_subscript_from_sar + return var("&")(self.rec(get_start_subscript_from_sar(expr, self.kernel), type_context)) def map_subscript(self, expr, type_context): diff --git a/loopy/target/execution.py b/loopy/target/execution.py index 9d1d14376e5adc6592bfee29f738c4e37f6a39f6..96f6e065c9cabccbe48071d6d4be10a059813cf3 100644 --- a/loopy/target/execution.py +++ b/loopy/target/execution.py @@ -725,7 +725,7 @@ class KernelExecutorBase(object): self.packing_controller = SeparateArrayPackingController(program) self.output_names = tuple(arg.name for arg in self.program.args - if arg.is_output_only) + if arg.is_output) self.has_runtime_typed_args = any( arg.dtype is None diff --git a/loopy/transform/callable.py b/loopy/transform/callable.py index 4798436973557b9f730f7b3382480b2a2e63a095..1bbdb12010818d92b989f898ab874b10c5c2a31c 100644 --- a/loopy/transform/callable.py +++ b/loopy/transform/callable.py @@ -50,7 +50,7 @@ __doc__ = """ # {{{ register function lookup -def _resolved_callables_from_function_lookup(program, +def _resolve_callables_from_function_lookup(program, func_id_to_in_kernel_callable_mapper): """ Returns a copy of *program* with the expression nodes marked "Resolved" @@ -124,7 +124,7 @@ def register_function_id_to_in_knl_callable_mapper(program, new_func_id_mappers = program.func_id_to_in_knl_callable_mappers + ( [func_id_to_in_knl_callable_mapper]) - program = _resolved_callables_from_function_lookup(program, + program = _resolve_callables_from_function_lookup(program, func_id_to_in_knl_callable_mapper) new_program = program.copy( @@ -154,6 +154,95 @@ class _RegisterCalleeKernel(ImmutableRecord): return None +def _check_correctness_of_args_and_assignees(insn, callee_kernel): + """ + Checks that -- + 1. the call in *insn* agrees the :attr:`~loopy.ArrayArg.is_input` and + :attr:`~loopy.ArrayArg.is_output` for the corresponding arguments in + *callee_kernel*, + 2. the call does not get multiple values for a keyword argument, + 3. only the arguments that are both output and input appear in the + assignees as well as parameters in *insn*'s call. + """ + from loopy.kernel.function_interface import get_kw_pos_association + kw_to_pos, pos_to_kw = get_kw_pos_association(callee_kernel) + + # mapping from argument index in callee to the assignees/paramters mapping + # to it + callee_args_to_insn_params = [[] for _ in callee_kernel.args] + expr = insn.expression + from pymbolic.primitives import Call + if isinstance(expr, Call): + expr = CallWithKwargs(expr.function, expr.parameters, kw_parameters={}) + + # {{{ check that call parameters are input arguments in callee + + for i, param in enumerate(expr.parameters): + pos = kw_to_pos[callee_kernel.args[i].name] + if pos < 0: + raise LoopyError("#{}(1-based) argument meant for output obtained as an" + " input in '{}'.".format(i+1, insn)) + + assert pos == i + + callee_args_to_insn_params[i].append(param) + + for kw, param in six.iteritems(expr.kw_parameters): + pos = kw_to_pos[kw] + if pos < 0: + raise LoopyError("Keyword argument '{}' meant for output obtained as an" + " input in '{}'.".format(kw, insn)) + callee_args_to_insn_params[pos].append(param) + + # }}} + + # {{{ check that positional and Keyword arguments and positional do not map + # to the same callee arg + + if any(len(pars) >= 2 for pars in callee_args_to_insn_params): + raise LoopyError("{}() got multiple values for keyword argument" + " '{}'".format(callee_kernel.name, callee_kernel.args[i].name)) + + # }}} + + # {{{ check that only the args which are both input and output appear both + # in assignees and parameters + + num_pure_assignees = 0 + for i, assignee in enumerate(insn.assignees): + pos = kw_to_pos[pos_to_kw[-i-1]] + + if pos < 0: + pos = (len(expr.parameters) + + len(expr.kw_parameters)+num_pure_assignees) + num_pure_assignees += 1 + + callee_args_to_insn_params[pos].append(assignee) + + for arg, insn_params in zip(callee_kernel.args, + callee_args_to_insn_params): + if len(insn_params) == 1: + # making sure that the argument is either only input or output + if arg.is_input == arg.is_output: + raise LoopyError("Parameter '{}' in '{}' should be passed in" + " both assignees and parameters in Call.".format( + insn_params[0], insn)) + elif len(insn_params) == 2: + if arg.is_input != arg.is_output: + raise LoopyError("Found multiple parameters mapping to an" + " argument which is not both input and output in" + " ''.".format()) + if insn_params[0] != insn_params[1]: + raise LoopyError("Unequal SubArrayRefs '{}', '{}' passed as '{}'" + " to '{}'.".format(insn_params[0], insn_params[1], + arg.name, callee_kernel.name)) + else: + # should not reach here + assert False + + # }}} + + def register_callable_kernel(program, callee_kernel): """Returns a copy of *caller_kernel*, which would resolve *function_name* in an expression as a call to *callee_kernel*. @@ -169,37 +258,13 @@ def register_callable_kernel(program, callee_kernel): assert isinstance(callee_kernel, LoopKernel), ('{0} !=' '{1}'.format(type(callee_kernel), LoopKernel)) - # check to make sure that the variables with 'out' direction is equal to - # the number of assigness in the callee kernel intructions. - expected_num_assignees = len([arg for arg in callee_kernel.args if - arg.name in callee_kernel.get_written_variables()]) - expected_num_parameters = len([arg for arg in callee_kernel.args if - arg.name in callee_kernel.get_read_variables()]) + len( - [arg for arg in callee_kernel.args if arg.name not in - (callee_kernel.get_read_variables() | - callee_kernel.get_written_variables())]) for in_knl_callable in program.callables_table.values(): if isinstance(in_knl_callable, CallableKernel): caller_kernel = in_knl_callable.subkernel for insn in caller_kernel.instructions: if isinstance(insn, CallInstruction) and ( insn.expression.function.name == callee_kernel.name): - if isinstance(insn.expression, CallWithKwargs): - kw_parameters = insn.expression.kw_parameters - else: - kw_parameters = {} - if len(insn.assignees) != expected_num_assignees: - raise LoopyError("The number of arguments with 'out' " - "direction " "in callee kernel %s and the number " - "of assignees in " "instruction %s do not " - "match." % ( - callee_kernel.name, insn.id)) - if len(insn.expression.parameters+tuple( - kw_parameters.values())) != expected_num_parameters: - raise LoopyError("The number of expected arguments " - "for the callee kernel %s and the number of " - "parameters in instruction %s do not match." - % (callee_kernel.name, insn.id)) + _check_correctness_of_args_and_assignees(insn, callee_kernel) elif isinstance(insn, (MultiAssignmentBase, CInstruction, _DataObliviousInstruction)): @@ -291,7 +356,9 @@ class KernelInliner(SubstitutionMapper): "constant shape.".format(callee_arg)) flatten_index = 0 - for i, idx in enumerate(sar.get_begin_subscript().index_tuple): + from loopy.symbolic import get_start_subscript_from_sar + for i, idx in enumerate(get_start_subscript_from_sar(sar, + self.caller).index_tuple): flatten_index += idx*caller_arg.dim_tags[i].stride flatten_index += sum( @@ -377,8 +444,6 @@ def _inline_call_instruction(caller_kernel, callee_knl, instruction): parameters = instruction.expression.parameters # reads # add keyword parameters - from pymbolic.primitives import CallWithKwargs - if isinstance(instruction.expression, CallWithKwargs): from loopy.kernel.function_interface import get_kw_pos_association @@ -390,7 +455,7 @@ def _inline_call_instruction(caller_kernel, callee_knl, instruction): assignee_pos = 0 parameter_pos = 0 for i, arg in enumerate(callee_knl.args): - if arg.is_output_only: + if arg.is_output: arg_map[arg.name] = assignees[assignee_pos] assignee_pos += 1 else: diff --git a/loopy/transform/fusion.py b/loopy/transform/fusion.py index ac0c5c83eb6729422c58fe52bba56b6a193ad494..921117f9ed6f5a0c4ca54d04e15e94f25237f3cb 100644 --- a/loopy/transform/fusion.py +++ b/loopy/transform/fusion.py @@ -32,8 +32,6 @@ from loopy.diagnostic import LoopyError from pymbolic import var from loopy.kernel import LoopKernel -from loopy.kernel.function_interface import CallableKernel -from loopy.program import rename_resolved_functions_in_a_single_kernel def _apply_renames_in_exprs(kernel, var_renames): @@ -291,7 +289,51 @@ def _fuse_two_kernels(knla, knlb): # }}} -def fuse_loop_kernels(kernels, suffixes=None, data_flow=None): +def fuse_kernels(kernels, suffixes=None, data_flow=None): + """Return a kernel that performs all the operations in all entries + of *kernels*. + + :arg kernels: A list of :class:`loopy.LoopKernel` instances to be fused. + :arg suffixes: If given, must be a list of strings of a length matching + that of *kernels*. This will be used to disambiguate the names + of temporaries, as described below. + :arg data_flow: A list of data dependencies + ``[(var_name, from_kernel, to_kernel), ...]``. + Based on this, the fuser will create dependencies between all + writers of *var_name* in ``kernels[from_kernel]`` to + readers of *var_name* in ``kernels[to_kernel]``. + *from_kernel* and *to_kernel* are indices into *kernels*. + + The components of the kernels are fused as follows: + + * The resulting kernel will have a domain involving all the inames + and parameters occurring across *kernels*. + Inames with matching names across *kernels* are fused in such a way + that they remain a single iname in the fused kernel. + Use :func:`loopy.rename_iname` if this is not desired. + + * The projection of the domains of each pair of kernels onto their + common subset of inames must match in order for fusion to + succeed. + + * Assumptions are fused by taking their conjunction. + + * If kernel arguments with matching names are encountered across + *kernels*, their declarations must match in order for fusion to + succeed. + + * Temporaries are automatically renamed to remain uniquely associated + with each instruction stream. + + * The resulting kernel will contain all instructions from each entry + of *kernels*. Clashing instruction IDs will be renamed to ensure + uniqueness. + + .. versionchanged:: 2016.2 + + *data_flow* was added in version 2016.2 + """ + assert all(isinstance(knl, LoopKernel) for knl in kernels) kernels = list(kernels) @@ -373,101 +415,4 @@ def fuse_loop_kernels(kernels, suffixes=None, data_flow=None): return result - -def fuse_kernels(programs, suffixes=None, data_flow=None): - """Return a kernel that performs all the operations in all entries - of *kernels*. - - :arg kernels: A list of :class:`loopy.LoopKernel` instances to be fused. - :arg suffixes: If given, must be a list of strings of a length matching - that of *kernels*. This will be used to disambiguate the names - of temporaries, as described below. - :arg data_flow: A list of data dependencies - ``[(var_name, from_kernel, to_kernel), ...]``. - Based on this, the fuser will create dependencies between all - writers of *var_name* in ``kernels[from_kernel]`` to - readers of *var_name* in ``kernels[to_kernel]``. - *from_kernel* and *to_kernel* are indices into *kernels*. - - The components of the kernels are fused as follows: - - * The resulting kernel will have a domain involving all the inames - and parameters occurring across *kernels*. - Inames with matching names across *kernels* are fused in such a way - that they remain a single iname in the fused kernel. - Use :func:`loopy.rename_iname` if this is not desired. - - * The projection of the domains of each pair of kernels onto their - common subset of inames must match in order for fusion to - succeed. - - * Assumptions are fused by taking their conjunction. - - * If kernel arguments with matching names are encountered across - *kernels*, their declarations must match in order for fusion to - succeed. - - * Temporaries are automatically renamed to remain uniquely associated - with each instruction stream. - - * The resulting kernel will contain all instructions from each entry - of *kernels*. Clashing instruction IDs will be renamed to ensure - uniqueness. - - .. versionchanged:: 2016.2 - - *data_flow* was added in version 2016.2 - """ - - # all the resolved functions in programs must be registered in - # main_callables_table - main_prog_callables_info = ( - programs[0].callables_table) - old_root_kernel_callable = ( - programs[0].callables_table[programs[0].name]) - kernels = [programs[0].root_kernel] - - # removing the callable collisions that maybe present - for prog in programs[1:]: - root_kernel = prog.root_kernel - renames_needed = {} - for old_func_id, in_knl_callable in prog.callables_table.items(): - if isinstance(in_knl_callable, CallableKernel): - # Fusing programs with multiple callable kernels is tough. - # Reason: Need to first figure out the order in which the - # callable kernels must be resolved into - # main_callables_table, because of renaming is - # needed to be done in the callable kernels before registering. - # Hence disabling it until required. - if in_knl_callable.subkernel.name != prog.name: - raise LoopyError("fuse_kernels cannot fuse programs with " - "multiple callable kernels.") - - # root kernel are dealt at the end after performing all the - # renaming. - continue - main_prog_callables_info, new_func_id = ( - main_prog_callables_info.with_added_callable(var(old_func_id), - in_knl_callable)) - - if old_func_id != new_func_id: - renames_needed[old_func_id] = new_func_id - - if renames_needed: - root_kernel = rename_resolved_functions_in_a_single_kernel( - root_kernel, renames_needed) - - kernels.append(root_kernel) - - new_root_kernel = fuse_loop_kernels(kernels, suffixes, data_flow) - new_root_kernel_callable = old_root_kernel_callable.copy( - subkernel=new_root_kernel.copy(name=programs[0].name)) - - # TODO: change the name of the final root kernel. - main_prog_callables_info, _ = main_prog_callables_info.with_added_callable( - var(programs[0].name), new_root_kernel_callable) - - return programs[0].copy( - callables_table=main_prog_callables_info) - # vim: foldmethod=marker diff --git a/loopy/transform/make_scalar.py b/loopy/transform/make_scalar.py index ab91fdf78068fda7ee27cf8543419c22ae723024..d0e7d1bc2ec5d1b5815ec8c8c30fecc198014c86 100644 --- a/loopy/transform/make_scalar.py +++ b/loopy/transform/make_scalar.py @@ -23,7 +23,7 @@ def make_scalar(kernel, var_name): kernel = ScalarChanger(rule_mapping_context, var_name).map_kernel(kernel) new_args = [ValueArg(arg.name, arg.dtype, target=arg.target, - is_output_only=arg.is_output_only) if arg.name == var_name else arg for + is_output=arg.is_output) if arg.name == var_name else arg for arg in kernel.args] new_temps = dict((tv.name, tv.copy(shape=(), dim_tags=None)) if tv.name == var_name else (tv.name, tv) for tv in diff --git a/loopy/type_inference.py b/loopy/type_inference.py index 281dcb43dd3609322fbda5bfc7af4f37125872fa..0d4430e0dd61f35d6c53d8d176449fbd67722cf9 100644 --- a/loopy/type_inference.py +++ b/loopy/type_inference.py @@ -692,7 +692,7 @@ class TypeInferenceMapper(CombineMapper): for rec_result in rec_results] def map_sub_array_ref(self, expr): - return self.rec(expr.get_begin_subscript()) + return self.rec(expr.subscript) # }}} diff --git a/test/test_callables.py b/test/test_callables.py index f2f3acbd67dc792e6f5c1aa7cd2581896b3ca024..04eeae66666a85d3cf4dc9c7f57455967c2992ef 100644 --- a/test/test_callables.py +++ b/test/test_callables.py @@ -63,38 +63,35 @@ def test_register_function_lookup(ctx_factory): def test_register_knl(ctx_factory, inline): ctx = ctx_factory() queue = cl.CommandQueue(ctx) - n = 2 ** 4 + n = 4 x = np.random.rand(n, n, n, n, n) y = np.random.rand(n, n, n, n, n) grandchild_knl = lp.make_function( - "{[i, j]:0<= i, j< 16}", + "{[i, j]:0<= i, j< 4}", """ c[i, j] = 2*a[i, j] + 3*b[i, j] """, name='linear_combo1') child_knl = lp.make_function( - "{[i, j]:0<=i, j < 16}", + "{[i, j]:0<=i, j < 4}", """ [i, j]: g[i, j] = linear_combo1([i, j]: e[i, j], [i, j]: f[i, j]) """, name='linear_combo2') parent_knl = lp.make_kernel( - "{[i, j, k, l, m]: 0<=i, j, k, l, m<16}", + "{[i, j, k, l, m]: 0<=i, j, k, l, m<4}", """ [j, l]: z[i, j, k, l, m] = linear_combo2([j, l]: x[i, j, k, l, m], [j, l]: y[i, j, k, l, m]) """, kernel_data=[ lp.GlobalArg( - name='x', + name='x, y', dtype=np.float64, - shape=(16, 16, 16, 16, 16)), - lp.GlobalArg( - name='y', - dtype=np.float64, - shape=(16, 16, 16, 16, 16)), '...'], + shape=(n, n, n, n, n)), + '...'] ) knl = lp.register_callable_kernel( @@ -115,36 +112,29 @@ def test_register_knl(ctx_factory, inline): def test_slices_with_negative_step(ctx_factory, inline): ctx = ctx_factory() queue = cl.CommandQueue(ctx) - n = 2 ** 4 + n = 4 x = np.random.rand(n, n, n, n, n) y = np.random.rand(n, n, n, n, n) child_knl = lp.make_function( - "{[i, j]:0<=i, j < 16}", + "{[i, j]:0<=i, j < 4}", """ g[i, j] = 2*e[i, j] + 3*f[i, j] """, name="linear_combo") parent_knl = lp.make_kernel( - "{[i, k, m]: 0<=i, k, m<16}", + "{[i, k, m]: 0<=i, k, m<4}", """ - z[i, 15:-1:-1, k, :, m] = linear_combo(x[i, :, k, :, m], + z[i, 3:-1:-1, k, :, m] = linear_combo(x[i, :, k, :, m], y[i, :, k, :, m]) """, kernel_data=[ lp.GlobalArg( - name='x', - dtype=np.float64, - shape=(16, 16, 16, 16, 16)), - lp.GlobalArg( - name='y', - dtype=np.float64, - shape=(16, 16, 16, 16, 16)), - lp.GlobalArg( - name='z', + name='x, y, z', dtype=np.float64, - shape=(16, 16, 16, 16, 16)), '...'], + shape=(n, n, n, n, n)), + '...'] ) knl = lp.register_callable_kernel( @@ -163,7 +153,7 @@ def test_register_knl_with_call_with_kwargs(ctx_factory, inline): ctx = ctx_factory() queue = cl.CommandQueue(ctx) - n = 2 ** 2 + n = 4 a_dev = cl.clrandom.rand(queue, (n, n, n, n, n), np.float32) b_dev = cl.clrandom.rand(queue, (n, n, n, n, n), np.float32) @@ -215,27 +205,27 @@ def test_register_knl_with_hw_axes(ctx_factory, inline): ctx = ctx_factory() queue = cl.CommandQueue(ctx) - n = 2 ** 5 + n = 4 x_dev = cl.clrandom.rand(queue, (n, n, n, n, n), np.float64) y_dev = cl.clrandom.rand(queue, (n, n, n, n, n), np.float64) callee_knl = lp.make_function( - "{[i, j]:0<=i, j < 32}", + "{[i, j]:0<=i, j < 4}", """ g[i, j] = 2*e[i, j] + 3*f[i, j] """, name='linear_combo') - callee_knl = lp.split_iname(callee_knl, "i", 2, inner_tag="l.0", outer_tag="g.0") + callee_knl = lp.split_iname(callee_knl, "i", 1, inner_tag="l.0", outer_tag="g.0") caller_knl = lp.make_kernel( - "{[i, j, k, l, m]: 0<=i, j, k, l, m<32}", + "{[i, j, k, l, m]: 0<=i, j, k, l, m<4}", """ [j, l]: z[i, j, k, l, m] = linear_combo([j, l]: x[i, j, k, l, m], [j, l]: y[i, j, k, l, m]) """ ) - caller_knl = lp.split_iname(caller_knl, "i", 8, inner_tag="l.1", outer_tag="g.1") + caller_knl = lp.split_iname(caller_knl, "i", 4, inner_tag="l.1", outer_tag="g.1") knl = lp.register_callable_kernel( caller_knl, callee_knl) @@ -252,8 +242,8 @@ def test_register_knl_with_hw_axes(ctx_factory, inline): x_host = x_dev.get() y_host = y_dev.get() - assert gsize == (16, 4) - assert lsize == (2, 8) + assert gsize == (4, 1) + assert lsize == (1, 4) assert np.linalg.norm(2*x_host+3*y_host-out['z'].get())/np.linalg.norm( 2*x_host+3*y_host) < 1e-15 @@ -270,19 +260,19 @@ def test_shape_translation_through_sub_array_ref(ctx_factory, inline): callee1 = lp.make_function( "{[i]: 0<=i<6}", """ - a[i] = 2*abs(b[i]) + b[i] = 2*abs(a[i]) """, name="callee_fn1") callee2 = lp.make_function( "{[i, j]: 0<=i<3 and 0 <= j < 2}", """ - a[i, j] = 3*b[i, j] + b[i, j] = 3*a[i, j] """, name="callee_fn2") callee3 = lp.make_function( "{[i]: 0<=i<6}", """ - a[i] = 5*b[i] + b[i] = 5*a[i] """, name="callee_fn3") knl = lp.make_kernel( @@ -337,6 +327,10 @@ def test_multi_arg_array_call(ctx_factory): lp.Assignment(id="update", assignee=acc_i, expression=p.Variable("min")(acc_i, a_i), depends_on="init1,init2")], + [ + lp.GlobalArg('a'), + lp.GlobalArg('acc_i, index', is_input=False, is_output=True), + "..."], name="custom_argmin") argmin_kernel = lp.fix_parameters(argmin_kernel, n=n) @@ -370,13 +364,13 @@ def test_packing_unpacking(ctx_factory, inline): callee1 = lp.make_function( "{[i]: 0<=i<6}", """ - a[i] = 2*b[i] + b[i] = 2*a[i] """, name="callee_fn1") callee2 = lp.make_function( "{[i, j]: 0<=i<2 and 0 <= j < 3}", """ - a[i, j] = 3*b[i, j] + b[i, j] = 3*a[i, j] """, name="callee_fn2") knl = lp.make_kernel( @@ -413,21 +407,22 @@ def test_non_sub_array_refs_arguments(ctx_factory): from loopy.transform.callable import _match_caller_callee_argument_dimension_ callee = lp.make_function("{[i] : 0 <= i < 6}", "a[i] = a[i] + j", - [lp.GlobalArg("a", dtype="double", shape=(6,), is_output_only=False), + [lp.GlobalArg("a", dtype="double", shape=(6,), is_output=True, + is_input=True), lp.ValueArg("j", dtype="int")], name="callee") caller1 = lp.make_kernel("{[j] : 0 <= j < 2}", "a[:] = callee(a[:], b[0])", - [lp.GlobalArg("a", dtype="double", shape=(6, ), is_output_only=False), - lp.GlobalArg("b", dtype="double", shape=(1, ), is_output_only=False)], + [lp.GlobalArg("a", dtype="double", shape=(6, ), is_output=False), + lp.GlobalArg("b", dtype="double", shape=(1, ), is_output=False)], name="caller", target=lp.CTarget()) caller2 = lp.make_kernel("{[j] : 0 <= j < 2}", "a[:]=callee(a[:], 3.1415926)", [lp.GlobalArg("a", dtype="double", shape=(6, ), - is_output_only=False)], + is_output=False)], name="caller", target=lp.CTarget()) caller3 = lp.make_kernel("{[j] : 0 <= j < 2}", "a[:]=callee(a[:], kappa)", [lp.GlobalArg("a", dtype="double", shape=(6, ), - is_output_only=False), '...'], + is_output=False), '...'], name="caller", target=lp.CTarget()) registered = lp.register_callable_kernel(caller1, callee) @@ -461,8 +456,7 @@ def test_empty_sub_array_refs(ctx_factory, inline): callee = lp.make_function( "{[d]:0<=d<1}", """ - a[d] = b[d] - c[d] - + c[d] = a[d] - b[d] """, name='wence_function') caller = lp.make_kernel("{[i]: 0<=i<10}", @@ -484,13 +478,13 @@ def test_empty_sub_array_refs(ctx_factory, inline): def test_array_inputs_to_callee_kernels(ctx_factory, inline): ctx = ctx_factory() queue = cl.CommandQueue(ctx) - n = 2 ** 4 + n = 2 ** 3 x = np.random.rand(n, n) y = np.random.rand(n, n) child_knl = lp.make_function( - "{[i, j]:0<=i, j < 16}", + "{[i, j]:0<=i, j < 8}", """ g[i, j] = 2*e[i, j] + 3*f[i, j] """, name="linear_combo") @@ -502,17 +496,10 @@ def test_array_inputs_to_callee_kernels(ctx_factory, inline): """, kernel_data=[ lp.GlobalArg( - name='x', - dtype=np.float64, - shape=(16, 16)), - lp.GlobalArg( - name='y', - dtype=np.float64, - shape=(16, 16)), - lp.GlobalArg( - name='z', + name='x, y, z', dtype=np.float64, - shape=(16, 16)), '...'], + shape=(n, n)), + '...'] ) knl = lp.register_callable_kernel( @@ -581,6 +568,52 @@ def test_unknown_stride_to_callee(): print(lp.generate_code_v2(prog).device_code()) +def test_argument_matching_for_inplace_update(ctx_factory): + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + twice = lp.make_function( + "{[i]: 0<=i<10}", + """ + x[i] = 2*x[i] + """, name='twice') + + knl = lp.make_kernel( + "{:}", + """ + x[:] = twice(x[:]) + """, [lp.GlobalArg('x', shape=(10,), dtype=np.float64)]) + + knl = lp.register_callable_kernel(knl, twice) + + x = np.random.randn(10) + evt, (out, ) = knl(queue, x=np.copy(x)) + + assert np.allclose(2*x, out) + + +def test_non_zero_start_in_subarray_ref(ctx_factory): + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + twice = lp.make_function( + "{[i]: 0<=i<10}", + """ + b[i] = 2*a[i] + """, name='twice') + + knl = lp.make_kernel( + "{[i, j]: -5<=i<5 and 0<=j<10}", + """ + [i]:y[i+5] = twice([j]: x[j]) + """, [lp.GlobalArg('x, y', shape=(10,), dtype=np.float64)]) + + knl = lp.register_callable_kernel(knl, twice) + + x = np.random.randn(10) + evt, (out, ) = knl(queue, x=np.copy(x)) + + assert np.allclose(2*x, out) + + if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) diff --git a/test/test_fortran.py b/test/test_fortran.py index 9d99471cbf4c1bafe502505b3981255c1bf7ba30..2e67116969d6b5f8fd8d7854bc2617431e3c14d9 100644 --- a/test/test_fortran.py +++ b/test/test_fortran.py @@ -538,6 +538,7 @@ def test_parse_and_fuse_two_kernels(): !$loopy begin ! + ! # FIXME: correct this after the "TranslationUnit" is done. ! prg = lp.parse_fortran(SOURCE) ! fill = prg["fill"] ! twice = prg["twice"] diff --git a/test/test_numa_diff.py b/test/test_numa_diff.py index 1ba44e77e13a88ecbc05f4eecc6b9c7e397eb656..de0bcf70a7f3f86152e86524486e2730522df325 100644 --- a/test/test_numa_diff.py +++ b/test/test_numa_diff.py @@ -60,7 +60,8 @@ def test_gnuma_horiz_kernel(ctx_factory, ilp_multiple, Nq, opt_level): # noqa source = source.replace("datafloat", "real*4") hsv_r, hsv_s = [ - knl for knl in lp.parse_fortran(source, filename, seq_dependencies=False) + knl for knl in lp.parse_fortran(source, filename, + seq_dependencies=False) if "KernelR" in knl.name or "KernelS" in knl.name ] hsv_r = lp.tag_instructions(hsv_r, "rknl") @@ -229,6 +230,15 @@ def test_gnuma_horiz_kernel(ctx_factory, ilp_multiple, Nq, opt_level): # noqa hsv = tap_hsv + hsv = lp.set_options(hsv, + ignore_boostable_into=True, + cl_build_options=[ + "-cl-denorms-are-zero", + "-cl-fast-relaxed-math", + "-cl-finite-math-only", + "-cl-mad-enable", + "-cl-no-signed-zeros"]) + if 1: print("OPS") op_map = lp.get_op_map(hsv, subgroup_size=32) @@ -238,14 +248,6 @@ def test_gnuma_horiz_kernel(ctx_factory, ilp_multiple, Nq, opt_level): # noqa gmem_map = lp.get_mem_access_map(hsv, subgroup_size=32).to_bytes() print(lp.stringify_stats_mapping(gmem_map)) - hsv = lp.set_options(hsv, cl_build_options=[ - "-cl-denorms-are-zero", - "-cl-fast-relaxed-math", - "-cl-finite-math-only", - "-cl-mad-enable", - "-cl-no-signed-zeros", - ]) - # FIXME: renaming's a bit tricky in this program model. # add a simple transformation for it # hsv = hsv.copy(name="horizontalStrongVolumeKernel")