diff --git a/doc/ref_kernel.rst b/doc/ref_kernel.rst index f399d812e..922315685 100644 --- a/doc/ref_kernel.rst +++ b/doc/ref_kernel.rst @@ -493,6 +493,12 @@ Barrier Instructions .. autoclass:: BarrierInstruction +Instruction Tags +^^^^^^^^^^^^^^^^ + +.. autoclass:: LegacyStringInstructionTag +.. autoclass:: UseStreamingStoreTag + .. }}} Data: Arguments and Temporaries diff --git a/loopy/__init__.py b/loopy/__init__.py index 66ba75024..9c4bfa6d0 100644 --- a/loopy/__init__.py +++ b/loopy/__init__.py @@ -32,6 +32,7 @@ default_function_mangler, single_arg_function_mangler) from loopy.kernel.instruction import ( + LegacyStringInstructionTag, UseStreamingStoreTag, MemoryOrdering, memory_ordering, MemoryScope, memory_scope, VarAtomicity, OrderedAtomic, AtomicInit, AtomicUpdate, @@ -155,6 +156,7 @@ "LoopKernel", "KernelState", "kernel_state", # lower case is deprecated + "LegacyStringInstructionTag", "UseStreamingStoreTag", "MemoryOrdering", "memory_ordering", # lower case is deprecated "MemoryScope", "memory_scope", # lower case is deprecated diff --git a/loopy/frontend/fortran/translator.py b/loopy/frontend/fortran/translator.py index ff357ae8a..78eddfb54 100644 --- a/loopy/frontend/fortran/translator.py +++ b/loopy/frontend/fortran/translator.py @@ -34,6 +34,7 @@ from islpy import dim_type from loopy.symbolic import IdentityMapper from loopy.diagnostic import LoopyError +from loopy.kernel.instruction import LegacyStringInstructionTag from pymbolic.primitives import Wildcard @@ -640,16 +641,16 @@ def map_Comment(self, node): stripped_comment_line) if begin_tag_match: - tag = begin_tag_match.group(1) + tag = LegacyStringInstructionTag(begin_tag_match.group(1)) if tag in self.instruction_tags: - raise TranslationError("nested begin tag for tag '%s'" % tag) + raise TranslationError(f"nested begin tag for tag '{tag.value}'") self.instruction_tags.append(tag) elif end_tag_match: - tag = end_tag_match.group(1) + tag = LegacyStringInstructionTag(end_tag_match.group(1)) if tag not in self.instruction_tags: raise TranslationError( - "end tag without begin tag for tag '%s'" % tag) + f"end tag without begin tag for tag '{tag.value}'") self.instruction_tags.remove(tag) elif faulty_loopy_pragma_match is not None: diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index 682bbc0fa..f4b3f673b 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -72,6 +72,38 @@ def __init__(self, name): # }}} +# {{{ tag normalization + +def _normalize_string_tag(tag): + from pytools.tag import Tag + + from loopy.kernel.instruction import ( + UseStreamingStoreTag, LegacyStringInstructionTag) + if tag == "!streaming_store": + return UseStreamingStoreTag() + else: + from pytools import resolve_name + try: + tag_cls = resolve_name(tag) + except ImportError: + pass + except AttributeError: + pass + else: + if issubclass(tag_cls, Tag): + return tag_cls() + + return LegacyStringInstructionTag(tag) + + +def _normalize_tags(tags): + return frozenset( + _normalize_string_tag(t) if isinstance(t, str) else t + for t in tags) + +# }}} + + # {{{ expand defines WORD_RE = re.compile(r"\b([a-zA-Z0-9_]+)\b") @@ -328,9 +360,9 @@ def parse_nosync_option(opt_value): del new_predicates elif opt_key == "tags" and opt_value is not None: - result["tags"] = frozenset( + result["tags"] = _normalize_tags([ tag.strip() for tag in opt_value.split(":") - if tag.strip()) + if tag.strip()]) elif opt_key == "atomic": if is_with_block: @@ -803,6 +835,10 @@ def intern_if_str(s): | insn_options_stack[-1]["conflicts_with_groups"]), **kwargs) + norm_tags = _normalize_tags(insn.tags) + if norm_tags != insn.tags: + insn = insn.copy(tags=norm_tags) + new_instructions.append(insn) inames_to_dup.append([]) diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index ac992bc35..81b174653 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -22,15 +22,59 @@ from sys import intern from pytools import ImmutableRecord, memoize_method +from pytools.tag import Tag, tag_dataclass, Taggable from loopy.diagnostic import LoopyError from loopy.tools import Optional from warnings import warn import islpy as isl +# {{{ instruction tags + +@tag_dataclass +class LegacyStringInstructionTag(Tag): + """A subclass of :class:`pytools.tag.Tag` for use in + :attr:`InstructionBase.tags` used for forward compatibility of the old + string-based tagging mechanism. String-based tags are automatically converted + to this type. + + .. attribute:: value + """ + value: str + + # FIXME: This class should be deprecated as soon as there is a viable + # alternative. For now, pattern matching and the textual syntax are + # only able to generate string tags, which is why the deprecation is not + # yet in effect. + + +@tag_dataclass +class UseStreamingStoreTag(Tag): + """A subclass of :class:`pytools.tag.Tag` for use in + :attr:`InstructionBase.tags` used to indicate that if the instruction is an + :class:`Assignment` or a :class:`CallInstruction`, then the 'store' part of + the assignment should be realized using streaming stores. + + .. note:: + + This tag is advisory in nature and may be ignored by targets + that do not understand it or in situations where it does not + apply. + + .. warning:: + + This is a dodgy shortcut, and no promise is made that this will + continue to work. Whether this is safe is target-dependent and + program-dependent. No promise of safety is made. + """ + pass + +# }}} + + # {{{ instructions: base class -class InstructionBase(ImmutableRecord): +class InstructionBase(ImmutableRecord, Taggable): """A base class for all types of instruction that can occur in a kernel. @@ -135,11 +179,11 @@ class InstructionBase(ImmutableRecord): .. attribute:: tags - A :class:`frozenset` of string identifiers that can be used to - identify groups of instructions. - - Tags starting with exclamation marks (``!``) are reserved and may have - specific meanings defined by :mod:`loopy` or its targets. + A :class:`frozenset` of subclasses of :class:`pytools.tag.Tag` used to + provide metadata on this object. Legacy string tags are converted to + :class:`LegacyStringInstructionTag` or, if they used to carry + a functional meaning, the tag carrying that same fucntional meaning + (e.g. :class:`UseStreamingStoreTag`). .. automethod:: __init__ .. automethod:: assignee_var_names @@ -148,6 +192,8 @@ class InstructionBase(ImmutableRecord): .. automethod:: write_dependency_names .. automethod:: dependency_names .. automethod:: copy + + Inherits from :class:`pytools.tag.Taggable`. """ # within_inames_is_final is deprecated and will be removed in version 2017.x. @@ -257,8 +303,13 @@ def __init__(self, id, depends_on, depends_on_is_final, within_inames=within_inames, priority=priority, predicates=predicates, + # Yes, tags is set by both this and the Taggable constructor. + # Here, we set it so that ImmutableRecord knows about it. + # The Taggable constructor call does extra validation. tags=tags) + Taggable.__init__(self, tags) + # {{{ abstract interface def read_dependency_names(self): @@ -347,7 +398,9 @@ def get_str_options(self): if self.priority: result.append("priority=%d" % self.priority) if self.tags: - result.append("tags=%s" % ":".join(self.tags)) + from loopy.kernel.tools import stringify_instruction_tag + result.append("tags=%s" % ":".join( + stringify_instruction_tag(t) for t in self.tags)) if hasattr(self, "atomicity") and self.atomicity: result.append("atomic=%s" % ":".join(str(a) for a in self.atomicity)) diff --git a/loopy/kernel/tools.py b/loopy/kernel/tools.py index e69eee471..d64b63fc7 100644 --- a/loopy/kernel/tools.py +++ b/loopy/kernel/tools.py @@ -1415,6 +1415,14 @@ def conform_to_uniform_length(s): # {{{ stringify_instruction_list +def stringify_instruction_tag(tag): + from loopy.kernel.instruction import LegacyStringInstructionTag + if isinstance(tag, LegacyStringInstructionTag): + return f"S({tag.value})" + else: + return str(tag) + + def stringify_instruction_list(kernel): # {{{ topological sort @@ -1529,7 +1537,8 @@ def adapt_to_new_inames_list(new_inames): if insn.priority: options.append("priority=%d" % insn.priority) if insn.tags: - options.append("tags=%s" % ":".join(insn.tags)) + options.append("tags=%s" % ":".join( + stringify_instruction_tag(t) for t in insn.tags)) if isinstance(insn, lp.Assignment) and insn.atomicity: options.append("atomic=%s" % ":".join( str(a) for a in insn.atomicity)) diff --git a/loopy/match.py b/loopy/match.py index 625d98d4d..48428636c 100644 --- a/loopy/match.py +++ b/loopy/match.py @@ -243,8 +243,16 @@ def __call__(self, kernel, matchable): class Tagged(GlobMatchExpressionBase): def __call__(self, kernel, matchable): + from loopy.kernel.instruction import LegacyStringInstructionTag if matchable.tags: - return any(self.re.match(tag) for tag in matchable.tags) + return any( + self.re.match(tag.value) + if isinstance(tag, LegacyStringInstructionTag) + else + + False + + for tag in matchable.tags) else: return False diff --git a/loopy/statistics.py b/loopy/statistics.py index 7e9bb36b5..0192aa27d 100755 --- a/loopy/statistics.py +++ b/loopy/statistics.py @@ -589,10 +589,11 @@ class MemAccess(Record): A :class:`str` that specifies the variable name of the data accessed. - .. attribute:: variable_tag + .. attribute:: variable_tags - A :class:`str` that specifies the variable tag of a - :class:`loopy.symbolic.TaggedVariable`. + A :class:`frozenset` of subclasses of :class:`~pytools.tag.Tag` + that reflects :attr:`~loopy.symbolic.TaggedVariable.tags` of + an accessed variable. .. attribute:: count_granularity @@ -610,7 +611,8 @@ class MemAccess(Record): """ def __init__(self, mtype=None, dtype=None, lid_strides=None, gid_strides=None, - direction=None, variable=None, variable_tag=None, + direction=None, variable=None, + *, variable_tags=None, variable_tag=None, count_granularity=None): if count_granularity not in CountGranularity.ALL+[None]: @@ -618,19 +620,51 @@ def __init__(self, mtype=None, dtype=None, lid_strides=None, gid_strides=None, "not allowed. count_granularity options: %s" % (count_granularity, CountGranularity.ALL+[None])) + # {{{ normalize variable_tags + + if variable_tags is not None and variable_tag is not None: + raise TypeError( + "may not specify both 'variable_tags' and 'variable_tag'") + if variable_tag is not None: + from loopy.kernel.creation import _normalize_string_tag + variable_tags = frozenset({_normalize_string_tag(variable_tag)}) + + from warnings import warn + warn("Passing 'variable_tag' to MemAccess is deprecated and will " + "stop working in 2022. Pass variable_tags instead.") + + if variable_tags is None: + variable_tags = frozenset() + + # }}} + if dtype is None: Record.__init__(self, mtype=mtype, dtype=dtype, lid_strides=lid_strides, gid_strides=gid_strides, direction=direction, - variable=variable, variable_tag=variable_tag, + variable=variable, variable_tags=variable_tags, count_granularity=count_granularity) else: from loopy.types import to_loopy_type Record.__init__(self, mtype=mtype, dtype=to_loopy_type(dtype), lid_strides=lid_strides, gid_strides=gid_strides, direction=direction, variable=variable, - variable_tag=variable_tag, + variable_tags=variable_tags, count_granularity=count_granularity) + @property + def variable_tag(self): + from warnings import warn + warn("Accessing MemAccess.variable_tag is deprecated and will stop working " + "in 2022. Use MemAccess.variable_tags instead.", DeprecationWarning, + stacklevel=2) + + if len(self.variable_tags) != 1: + raise ValueError("cannot access MemAccess.variable_tag: access has " + f"{len(self.variable_tags)} tags") + + tag, = self.variable_tags + return tag + def __hash__(self): # Note that this means lid_strides and gid_strides must be sorted # in self.__repr__() @@ -647,7 +681,7 @@ def __repr__(self): sorted(self.gid_strides.items())), self.direction, self.variable, - self.variable_tag, + self.variable_tags, self.count_granularity) # }}} @@ -1031,9 +1065,9 @@ def map_variable(self, expr): def map_subscript(self, expr): name = expr.aggregate.name try: - var_tag = expr.aggregate.tag + var_tags = expr.aggregate.tags except AttributeError: - var_tag = None + var_tags = frozenset() if name in self.knl.arg_dict: array = self.knl.arg_dict[name] @@ -1062,7 +1096,7 @@ def map_subscript(self, expr): lid_strides=dict(sorted(lid_strides.items())), gid_strides=dict(sorted(gid_strides.items())), variable=name, - variable_tag=var_tag, + variable_tags=var_tags, count_granularity=count_granularity ): 1} ) + self.rec(expr.index_tuple) @@ -1678,7 +1712,7 @@ def get_mem_access_map(knl, numpy_types=True, count_redundant_work=False, gid_strides=mem_access.gid_strides, direction=mem_access.direction, variable=mem_access.variable, - variable_tag=mem_access.variable_tag, + variable_tags=mem_access.variable_tags, count_granularity=mem_access.count_granularity): ct for mem_access, ct in access_map.count_map.items()}, diff --git a/loopy/symbolic.py b/loopy/symbolic.py index b29f50161..29c99b2bf 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -29,6 +29,7 @@ from pytools import memoize, memoize_method, ImmutableRecord import pytools.lex +from pytools.tag import Taggable import pymbolic.primitives as p @@ -237,7 +238,7 @@ def map_reduction(self, expr, prec): self.rec(expr.expr, PREC_NONE)) def map_tagged_variable(self, expr, prec): - return f"{expr.name}${expr.tag}" + return f"{expr.name}${{{', '.join(str(t) for t in expr.tags)}}}" def map_linear_subscript(self, expr, enclosing_prec): from pymbolic.mapper.stringifier import PREC_CALL, PREC_NONE @@ -311,7 +312,7 @@ def map_tagged_variable(self, expr, other, urecs): # Check if the variables match literally--that's ok, too. if (isinstance(other, TaggedVariable) and expr.name == other.name - and expr.tag == other.tag + and expr.tags == other.tags and expr.name not in self.lhs_mapping_candidates): return urecs else: @@ -555,21 +556,53 @@ def __getinitargs__(self): mapper_method = intern("map_type_cast") -class TaggedVariable(LoopyExpressionBase, p.Variable): - """This is an identifier with a tag, such as 'matrix$one', where +class TaggedVariable(LoopyExpressionBase, p.Variable, Taggable): + """This is an identifier with tags, such as ``matrix$one``, where 'one' identifies this specific use of the identifier. This mechanism may then be used to address these uses--such as by prefetching only accesses tagged a certain way. + + .. attribute:: tags + + A :class:`frozenset` of subclasses of :class:`pytools.tag.Tag` used to + provide metadata on this object. Legacy string tags are converted to + :class:`~loopy.LegacyStringInstructionTag` or, if they used to carry + a functional meaning, the tag carrying that same fucntional meaning + (e.g. :class:`~loopy.UseStreamingStoreTag`). + + Inherits from :class:`pymbolic.primitives.Variable` + and :class:`pytools.tag.Taggable`. """ - init_arg_names = ("name", "tag") + init_arg_names = ("name", "tags") + + def __init__(self, name, tags): + p.Variable.__init__(self, name) + if isinstance(tags, str): + from loopy.kernel.creation import _normalize_string_tag + tags = frozenset({_normalize_string_tag(tags)}) + + assert isinstance(tags, frozenset) + assert tags - def __init__(self, name, tag): - super().__init__(name) - self.tag = tag + Taggable.__init__(self, tags) + + @property + def tag(self): + from warnings import warn + warn("Accessing TaggedVariable.tag is deprecated and will stop working " + "in 2022. Use TaggedVariable.tags instead.", DeprecationWarning, + stacklevel=2) + + if len(self.tags) != 1: + raise ValueError("cannot access TaggedVariable.tag: variable has " + f"{len(self.tags)} tags") + + tag, = self.tags + return tag def __getinitargs__(self): - return self.name, self.tag + return self.name, self.tags mapper_method = intern("map_tagged_variable") @@ -719,7 +752,7 @@ def get_dependencies(expr): def parse_tagged_name(expr): if isinstance(expr, TaggedVariable): - return expr.name, expr.tag + return expr.name, expr.tags elif isinstance(expr, p.Variable): return expr.name, None else: @@ -759,30 +792,30 @@ def map_call(self, expr): if not isinstance(expr.function, p.Variable): return IdentityMapper.map_call(self, expr) - name, tag = parse_tagged_name(expr.function) + name, tags = parse_tagged_name(expr.function) new_name = self.renames.get(name) if new_name is None: return IdentityMapper.map_call(self, expr) - if tag is None: - sym = p.Variable(new_name) + if tags: + sym = TaggedVariable(new_name, tags) else: - sym = TaggedVariable(new_name, tag) + sym = p.Variable(new_name) return type(expr)(sym, tuple(self.rec(child) for child in expr.parameters)) def map_variable(self, expr): - name, tag = parse_tagged_name(expr) + name, tags = parse_tagged_name(expr) new_name = self.renames.get(name) if new_name is None: return IdentityMapper.map_variable(self, expr) - if tag is None: - return p.Variable(new_name) + if tags: + return TaggedVariable(new_name, tags) else: - return TaggedVariable(new_name, tag) + return p.Variable(new_name) def rename_subst_rules_in_instructions(insns, renames): @@ -918,22 +951,22 @@ def __init__(self, rule_mapping_context): self.rule_mapping_context = rule_mapping_context def map_variable(self, expr, expn_state): - name, tag = parse_tagged_name(expr) + name, tags = parse_tagged_name(expr) if name not in self.rule_mapping_context.old_subst_rules: return IdentityMapper.map_variable(self, expr, expn_state) else: - return self.map_substitution(name, tag, (), expn_state) + return self.map_substitution(name, tags, (), expn_state) def map_call(self, expr, expn_state): if not isinstance(expr.function, p.Variable): return IdentityMapper.map_call(self, expr, expn_state) - name, tag = parse_tagged_name(expr.function) + name, tags = parse_tagged_name(expr.function) if name not in self.rule_mapping_context.old_subst_rules: return super().map_call(expr, expn_state) else: - return self.map_substitution(name, tag, self.rec( + return self.map_substitution(name, tags, self.rec( expr.parameters, expn_state), expn_state) @staticmethod @@ -948,16 +981,11 @@ def make_new_arg_context(rule_name, arg_names, arguments, arg_context): formal_arg_name: arg_subst_map(arg_value) for formal_arg_name, arg_value in zip(arg_names, arguments)} - def map_substitution(self, name, tag, arguments, expn_state): + def map_substitution(self, name, tags, arguments, expn_state): rule = self.rule_mapping_context.old_subst_rules[name] rec_arguments = self.rec(arguments, expn_state) - if tag is None: - tags = None - else: - tags = (tag,) - new_expn_state = expn_state.copy( stack=expn_state.stack + ((name, tags),), arg_context=self.make_new_arg_context( @@ -968,10 +996,10 @@ def map_substitution(self, name, tag, arguments, expn_state): new_name = self.rule_mapping_context.register_subst_rule( name, rule.arguments, result) - if tag is None: - sym = p.Variable(new_name) + if tags: + sym = TaggedVariable(new_name, tags) else: - sym = TaggedVariable(new_name, tag) + sym = p.Variable(new_name) if arguments: return sym(*rec_arguments) @@ -1091,12 +1119,7 @@ def __init__(self, rule_mapping_context, rules, within): self.rules = rules self.within = within - def map_substitution(self, name, tag, arguments, expn_state): - if tag is None: - tags = None - else: - tags = (tag,) - + def map_substitution(self, name, tags, arguments, expn_state): new_stack = expn_state.stack + ((name, tags),) if self.within(expn_state.kernel, expn_state.instruction, new_stack): @@ -1120,7 +1143,7 @@ def map_substitution(self, name, tag, arguments, expn_state): else: # do not expand return super().map_substitution( - name, tag, arguments, expn_state) + name, tags, arguments, expn_state) # }}} @@ -1890,7 +1913,7 @@ def map_variable(self, expr): def map_tagged_variable(self, expr): if expr.name in self.which_vars: - return TaggedVariable(expr.name+"'", expr.tag) + return TaggedVariable(expr.name+"'", expr.tags) else: return expr diff --git a/loopy/target/c/codegen/expression.py b/loopy/target/c/codegen/expression.py index 9ec99c784..d5666d34c 100644 --- a/loopy/target/c/codegen/expression.py +++ b/loopy/target/c/codegen/expression.py @@ -179,7 +179,7 @@ def base_impl(expr, type_context): def make_var(name): from loopy import TaggedVariable if isinstance(expr.aggregate, TaggedVariable): - return TaggedVariable(name, expr.aggregate.tag) + return TaggedVariable(name, expr.aggregate.tags) else: return var(name) diff --git a/loopy/target/ispc.py b/loopy/target/ispc.py index 416160461..c1fadc55c 100644 --- a/loopy/target/ispc.py +++ b/loopy/target/ispc.py @@ -392,7 +392,8 @@ def emit_assignment(self, codegen_state, insn): # {{{ handle streaming stores - if "!streaming_store" in insn.tags: + from loopy.kernel.instruction import UseStreamingStoreTag + if UseStreamingStoreTag() in insn.tags: ary = ecm.find_array(lhs) from loopy.kernel.array import get_access_info diff --git a/loopy/tools.py b/loopy/tools.py index 4fd093153..5be4ca6b5 100644 --- a/loopy/tools.py +++ b/loopy/tools.py @@ -64,9 +64,11 @@ class LoopyKeyBuilder(KeyBuilderBase): update_for_set = KeyBuilderBase.update_for_frozenset def update_for_dict(self, key_hash, key): - # Order matters for the hash--insert in sorted order. - for dict_key in sorted(key.keys()): - self.rec(key_hash, (dict_key, key[dict_key])) + from pytools import unordered_hash + unordered_hash( + key_hash, + (self.rec(self.new_hash(), (k, v)).digest() + for k, v in key.items())) update_for_defaultdict = update_for_dict @@ -601,4 +603,5 @@ def is_interned(s): def intern_frozenset_of_ids(fs): return frozenset(intern(s) for s in fs) + # vim: foldmethod=marker diff --git a/loopy/transform/data.py b/loopy/transform/data.py index 2319ea635..4851ffdec 100644 --- a/loopy/transform/data.py +++ b/loopy/transform/data.py @@ -258,21 +258,24 @@ def add_prefetch(kernel, var_name, sweep_inames=[], dim_arg_names=None, # }}} - # {{{ fish out tag + # {{{ fish out tags from loopy.symbolic import TaggedVariable if isinstance(parsed_var_name, TaggedVariable): var_name = parsed_var_name.name - tag = parsed_var_name.tag + tags = parsed_var_name.tags else: var_name = parsed_var_name.name - tag = None + tags = () # }}} c_name = var_name - if tag is not None: - c_name = c_name + "_" + tag + from loopy.kernel.instruction import LegacyStringInstructionTag + tag_suffix = "_".join(tag.value for tag in tags + if isinstance(tag, LegacyStringInstructionTag)) + if tag_suffix: + c_name = c_name + "_" + tag_suffix var_name_gen = kernel.get_var_name_generator() diff --git a/loopy/transform/instruction.py b/loopy/transform/instruction.py index 45ade0eca..055384ff1 100644 --- a/loopy/transform/instruction.py +++ b/loopy/transform/instruction.py @@ -209,11 +209,14 @@ def tag_instructions(kernel, new_tag, within=None): from loopy.match import parse_match within = parse_match(within) + from loopy.kernel.creation import _normalize_tags + new_tags = _normalize_tags([new_tag]) + new_insns = [] for insn in kernel.instructions: if within(kernel, insn): new_insns.append( - insn.copy(tags=insn.tags | frozenset([new_tag]))) + insn.copy(tags=insn.tags | new_tags)) else: new_insns.append(insn) diff --git a/setup.py b/setup.py index fcf284bc8..57f5e895d 100644 --- a/setup.py +++ b/setup.py @@ -84,7 +84,7 @@ def write_git_revision(package_name): python_requires="~=3.6", install_requires=[ - "pytools>=2021.1", + "pytools>=2021.2", "pymbolic>=2019.2", "genpy>=2016.1.2", "cgen>=2016.1", diff --git a/test/test_target.py b/test/test_target.py index e18f9f191..e935e32ac 100644 --- a/test/test_target.py +++ b/test/test_target.py @@ -356,7 +356,7 @@ def test_ispc_streaming_stores(): knl = lp.preprocess_kernel(knl) knl = lp.get_one_scheduled_kernel(knl) - lp.generate_code_v2(knl).all_code() + assert "streaming_store(" in lp.generate_code_v2(knl).all_code() def test_cuda_short_vector():