From 5c0c9f416915e086b6c337207443c00e51782601 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 17 Aug 2019 18:02:16 -0500 Subject: [PATCH 1/8] Fix cache handling of explicitly typed variables --- loopy/symbolic.py | 26 ++++++++++++++++++++++++++ loopy/tools.py | 3 ++- loopy/version.py | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/loopy/symbolic.py b/loopy/symbolic.py index 6aec1c995..03cee88db 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -191,6 +191,21 @@ class ConstantFoldingMapper(ConstantFoldingMapperBase, class StringifyMapper(StringifyMapperBase): + """ + .. note:: + + For the benefit of + :meth:`loopy.tools.LoopyEqKeyBuilder.update_for_pymbolic_field`, + this mapper satisfies the invariant + + ``mapper(expr_1) == mapper(expr_2)`` + if and only if + ``expr_1 == expr_2`` + """ + + def __init__(self): + super(StringifyMapper, self).__init__(constant_mapper=repr) + def map_literal(self, expr, *args): return expr.s @@ -203,6 +218,17 @@ class StringifyMapper(StringifyMapperBase): def map_local_hw_index(self, expr, enclosing_prec): return "loc.%d" % expr.index + def map_constant(self, expr, enclosing_prec): + if isinstance(expr, np.generic): + # Explicitly typed: Emitted string must reflect type exactly. + + # FIXME: This syntax cannot currently be parsed. + + return "%s(%s)" % (type(expr).__name__, repr(expr)) + else: + return super(StringifyMapper, self).map_constant( + expr, enclosing_prec) + def map_reduction(self, expr, prec): from pymbolic.mapper.stringifier import PREC_NONE diff --git a/loopy/tools.py b/loopy/tools.py index 0fc6d1bf9..05b9f514b 100644 --- a/loopy/tools.py +++ b/loopy/tools.py @@ -161,7 +161,8 @@ class LoopyEqKeyBuilder(object): self.field_dict[field_name] = value def update_for_pymbolic_field(self, field_name, value): - self.field_dict[field_name] = str(value).encode("utf-8") + from loopy.symbolic import StringifyMapper + self.field_dict[field_name] = StringifyMapper()(value).encode("utf-8") def key(self): """A key suitable for equality comparison.""" diff --git a/loopy/version.py b/loopy/version.py index 62940e8a7..66c7a0cf4 100644 --- a/loopy/version.py +++ b/loopy/version.py @@ -42,7 +42,7 @@ else: # }}} -VERSION = (2018, 1) +VERSION = (2019, 1) VERSION_STATUS = "" VERSION_TEXT = ".".join(str(x) for x in VERSION) + VERSION_STATUS -- GitLab From f74cae59747ce3df9485345445becde83fd2fc76 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 17 Aug 2019 18:03:36 -0500 Subject: [PATCH 2/8] Force typed literals to be generated for explicitly typed variables --- doc/ref_kernel.rst | 5 ++++ loopy/target/c/codegen/expression.py | 37 +++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/doc/ref_kernel.rst b/doc/ref_kernel.rst index 460633109..409cbef57 100644 --- a/doc/ref_kernel.rst +++ b/doc/ref_kernel.rst @@ -346,6 +346,11 @@ Loopy's expressions are a slight superset of the expressions supported by * ``cast(type, value)``: No parse syntax currently. See :class:`loopy.symbolic.TypeCast`. +* If constants in expressions are subclasses of :class:`numpy.generic`, + generated code will contain literals of exactly that type, making them + *explicitly typed*. Constants given as Python types such as :class:`int`, + :class:`float` or :class:`complex` are called *implicitly* typed and + adapt to the type of the expected result. TODO: Functions TODO: Reductions diff --git a/loopy/target/c/codegen/expression.py b/loopy/target/c/codegen/expression.py index 8ef921e44..fba5709e5 100644 --- a/loopy/target/c/codegen/expression.py +++ b/loopy/target/c/codegen/expression.py @@ -359,14 +359,16 @@ class ExpressionToCExpressionMapper(IdentityMapper): return cast(self.rec(expr.child, type_context)) def map_constant(self, expr, type_context): + from loopy.symbolic import Literal + if isinstance(expr, (complex, np.complexfloating)): try: dtype = expr.dtype except AttributeError: - # (COMPLEX_GUESS_LOGIC) - # This made it through type 'guessing' above, and it - # was concluded above (search for COMPLEX_GUESS_LOGIC), - # that nothing was lost by using single precision. + # (COMPLEX_GUESS_LOGIC) This made it through type 'guessing' in + # type inference, and it was concluded there (search for + # COMPLEX_GUESS_LOGIC in loopy.type_inference), that no + # accuracy was lost by using single precision. cast_type = "cfloat" else: if dtype == np.complex128: @@ -378,10 +380,33 @@ class ExpressionToCExpressionMapper(IdentityMapper): "generation: %s" % type(expr)) return var("%s_new" % cast_type)(expr.real, expr.imag) + elif isinstance(expr, np.generic): + # Explicitly typed: Generated code must reflect type exactly. + if isinstance(expr, np.float32): + return Literal(repr(expr)+"f") + + elif isinstance(expr, np.float64): + return Literal(repr(expr)) + + elif isinstance(expr, np.float128): + return Literal(repr(expr)+"l") + + elif isinstance(expr, np.integer): + suffix = "" + iinfo = np.iinfo(expr) + if iinfo.min < 0: + suffix += "u" + if iinfo.max > (2**31-1): + suffix += "l" + return Literal(repr(expr)+suffix) + + else: + raise LoopyError("do not know how to generate code for " + "constant of numpy type '%s'" % type(expr).__name__) + else: - from loopy.symbolic import Literal if type_context == "f": - return Literal(repr(float(expr))+"f") + return Literal(repr(np.float32(expr))+"f") elif type_context == "d": return Literal(repr(float(expr))) elif type_context == "i": -- GitLab From 2a10ecec43822d8ee835d0353342bf6ee543e306 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 17 Aug 2019 19:23:10 -0500 Subject: [PATCH 3/8] Move equality-preserving property of StringifyMapper to a subclass --- loopy/symbolic.py | 46 +++++++++++++++++++++++----------------------- loopy/tools.py | 5 +++-- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/loopy/symbolic.py b/loopy/symbolic.py index 03cee88db..424a457bf 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -191,18 +191,6 @@ class ConstantFoldingMapper(ConstantFoldingMapperBase, class StringifyMapper(StringifyMapperBase): - """ - .. note:: - - For the benefit of - :meth:`loopy.tools.LoopyEqKeyBuilder.update_for_pymbolic_field`, - this mapper satisfies the invariant - - ``mapper(expr_1) == mapper(expr_2)`` - if and only if - ``expr_1 == expr_2`` - """ - def __init__(self): super(StringifyMapper, self).__init__(constant_mapper=repr) @@ -218,17 +206,6 @@ class StringifyMapper(StringifyMapperBase): def map_local_hw_index(self, expr, enclosing_prec): return "loc.%d" % expr.index - def map_constant(self, expr, enclosing_prec): - if isinstance(expr, np.generic): - # Explicitly typed: Emitted string must reflect type exactly. - - # FIXME: This syntax cannot currently be parsed. - - return "%s(%s)" % (type(expr).__name__, repr(expr)) - else: - return super(StringifyMapper, self).map_constant( - expr, enclosing_prec) - def map_reduction(self, expr, prec): from pymbolic.mapper.stringifier import PREC_NONE @@ -261,6 +238,29 @@ class StringifyMapper(StringifyMapperBase): return "cast(%s, %s)" % (repr(expr.type), self.rec(expr.child, PREC_NONE)) +class EqualityPreservingStringifyMapper(StringifyMapperBase): + """ + For the benefit of + :meth:`loopy.tools.LoopyEqKeyBuilder.update_for_pymbolic_field`, + this mapper satisfies the invariant + + ``mapper(expr_1) == mapper(expr_2)`` + if and only if + ``expr_1 == expr_2`` + """ + + def map_constant(self, expr, enclosing_prec): + if isinstance(expr, np.generic): + # Explicitly typed: Emitted string must reflect type exactly. + + # FIXME: This syntax cannot currently be parsed. + + return "%s(%s)" % (type(expr).__name__, repr(expr)) + else: + return super(StringifyMapper, self).map_constant( + expr, enclosing_prec) + + class UnidirectionalUnifier(UnidirectionalUnifierBase): def map_reduction(self, expr, other, unis): if not isinstance(other, type(expr)): diff --git a/loopy/tools.py b/loopy/tools.py index 05b9f514b..33b6616f3 100644 --- a/loopy/tools.py +++ b/loopy/tools.py @@ -161,8 +161,9 @@ class LoopyEqKeyBuilder(object): self.field_dict[field_name] = value def update_for_pymbolic_field(self, field_name, value): - from loopy.symbolic import StringifyMapper - self.field_dict[field_name] = StringifyMapper()(value).encode("utf-8") + from loopy.symbolic import EqualityPreservingStringifyMapper + self.field_dict[field_name] = \ + EqualityPreservingStringifyMapper()(value).encode("utf-8") def key(self): """A key suitable for equality comparison.""" -- GitLab From 6e330a6bba49f4545774da6cc2925ed5a0690eeb Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 17 Aug 2019 19:23:48 -0500 Subject: [PATCH 4/8] Switch to/require pymbolic 2019.1 make_stringifier interface --- loopy/symbolic.py | 61 ++++++++++++++++------------------------------- setup.py | 2 +- 2 files changed, 21 insertions(+), 42 deletions(-) diff --git a/loopy/symbolic.py b/loopy/symbolic.py index 424a457bf..9ee96382f 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -357,7 +357,16 @@ class SubstitutionRuleExpander(IdentityMapper): # {{{ loopy-specific primitives -class Literal(p.Leaf): +class LoopyExpressionBase(p.Expression): + def stringifier(self): + from loopy.diagnostic import LoopyError + raise LoopyError("pymbolic < 2019.1 is in use. Please upgrade.") + + def make_stringifier(self, originating_stringifier=None): + return StringifyMapper() + + +class Literal(LoopyExpressionBase): """A literal to be used during code generation. .. note:: @@ -370,9 +379,6 @@ class Literal(p.Leaf): def __init__(self, s): self.s = s - def stringifier(self): - return StringifyMapper - def __getinitargs__(self): return (self.s,) @@ -381,7 +387,7 @@ class Literal(p.Leaf): mapper_method = "map_literal" -class ArrayLiteral(p.Leaf): +class ArrayLiteral(LoopyExpressionBase): """An array literal. .. note:: @@ -394,9 +400,6 @@ class ArrayLiteral(p.Leaf): def __init__(self, children): self.children = children - def stringifier(self): - return StringifyMapper - def __getinitargs__(self): return (self.children,) @@ -405,13 +408,10 @@ class ArrayLiteral(p.Leaf): mapper_method = "map_array_literal" -class HardwareAxisIndex(p.Leaf): +class HardwareAxisIndex(LoopyExpressionBase): def __init__(self, axis): self.axis = axis - def stringifier(self): - return StringifyMapper - def __getinitargs__(self): return (self.axis,) @@ -440,18 +440,15 @@ class LocalHardwareAxisIndex(HardwareAxisIndex): mapper_method = "map_local_hw_index" -class FunctionIdentifier(p.Leaf): +class FunctionIdentifier(LoopyExpressionBase): """A base class for symbols representing functions.""" init_arg_names = () - def stringifier(self): - return StringifyMapper - mapper_method = intern("map_loopy_function_identifier") -class TypedCSE(p.CommonSubexpression): +class TypedCSE(LoopyExpressionBase, p.CommonSubexpression): """A :class:`pymbolic.primitives.CommonSubexpression` annotated with a :class:`numpy.dtype`. """ @@ -467,7 +464,7 @@ class TypedCSE(p.CommonSubexpression): return dict(dtype=self.dtype) -class TypeAnnotation(p.Expression): +class TypeAnnotation(LoopyExpressionBase): """Undocumented for now. Currently only used internally around LHSs of assignments that create temporaries. """ @@ -480,13 +477,10 @@ class TypeAnnotation(p.Expression): def __getinitargs__(self): return (self.type, self.child) - def stringifier(self): - return StringifyMapper - mapper_method = intern("map_type_annotation") -class TypeCast(p.Expression): +class TypeCast(LoopyExpressionBase): """Only defined for numerical types with semantics matching :meth:`numpy.ndarray.astype`. @@ -523,13 +517,10 @@ class TypeCast(p.Expression): def __getinitargs__(self): return (self._type_name, self.child) - def stringifier(self): - return StringifyMapper - mapper_method = intern("map_type_cast") -class TaggedVariable(p.Variable): +class TaggedVariable(LoopyExpressionBase): """This is an identifier with a tag, 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 @@ -545,13 +536,10 @@ class TaggedVariable(p.Variable): def __getinitargs__(self): return self.name, self.tag - def stringifier(self): - return StringifyMapper - mapper_method = intern("map_tagged_variable") -class Reduction(p.Expression): +class Reduction(LoopyExpressionBase): """Represents a reduction operation on :attr:`exprs` across :attr:`inames`. @@ -638,9 +626,6 @@ class Reduction(p.Expression): and other.inames == self.inames and other.expr == self.expr) - def stringifier(self): - return StringifyMapper - @property def is_tuple_typed(self): return self.operation.arg_count > 1 @@ -653,7 +638,7 @@ class Reduction(p.Expression): mapper_method = intern("map_reduction") -class LinearSubscript(p.Expression): +class LinearSubscript(LoopyExpressionBase): """Represents a linear index into a multi-dimensional array, completely ignoring any multi-dimensional layout. """ @@ -667,13 +652,10 @@ class LinearSubscript(p.Expression): def __getinitargs__(self): return self.aggregate, self.index - def stringifier(self): - return StringifyMapper - mapper_method = intern("map_linear_subscript") -class RuleArgument(p.Expression): +class RuleArgument(LoopyExpressionBase): """Represents a (numbered) argument of a :class:`loopy.SubstitutionRule`. Only used internally in the rule-aware mappers to match subst rules independently of argument names. @@ -687,9 +669,6 @@ class RuleArgument(p.Expression): def __getinitargs__(self): return (self.index,) - def stringifier(self): - return StringifyMapper - mapper_method = intern("map_rule_argument") # }}} diff --git a/setup.py b/setup.py index 4229aeb45..ed19e0584 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ setup(name="loo.py", install_requires=[ "pytools>=2018.4", - "pymbolic>=2016.2", + "pymbolic>=2019.1", "genpy>=2016.1.2", "cgen>=2016.1", "islpy>=2016.2", -- GitLab From 15e44baf99e9a7a9b4e1135094bd4cc8fc90c9db Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 17 Aug 2019 19:24:18 -0500 Subject: [PATCH 5/8] Avoid trying to support 128-bit floats for now --- loopy/target/c/codegen/expression.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/loopy/target/c/codegen/expression.py b/loopy/target/c/codegen/expression.py index fba5709e5..a6742c225 100644 --- a/loopy/target/c/codegen/expression.py +++ b/loopy/target/c/codegen/expression.py @@ -382,14 +382,17 @@ class ExpressionToCExpressionMapper(IdentityMapper): return var("%s_new" % cast_type)(expr.real, expr.imag) elif isinstance(expr, np.generic): # Explicitly typed: Generated code must reflect type exactly. + + # FIXME: This assumes a 32-bit architecture. if isinstance(expr, np.float32): return Literal(repr(expr)+"f") elif isinstance(expr, np.float64): return Literal(repr(expr)) - elif isinstance(expr, np.float128): - return Literal(repr(expr)+"l") + # Disabled for now, possibly should be a subtarget. + # elif isinstance(expr, np.float128): + # return Literal(repr(expr)+"l") elif isinstance(expr, np.integer): suffix = "" -- GitLab From c5f92b08d46300925e7afc722baba804a711b7cf Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 17 Aug 2019 19:36:36 -0500 Subject: [PATCH 6/8] Fix super() call in EqualityPreservingStringifyMapper --- loopy/symbolic.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/loopy/symbolic.py b/loopy/symbolic.py index 9ee96382f..379ea7142 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -191,9 +191,6 @@ class ConstantFoldingMapper(ConstantFoldingMapperBase, class StringifyMapper(StringifyMapperBase): - def __init__(self): - super(StringifyMapper, self).__init__(constant_mapper=repr) - def map_literal(self, expr, *args): return expr.s @@ -249,6 +246,9 @@ class EqualityPreservingStringifyMapper(StringifyMapperBase): ``expr_1 == expr_2`` """ + def __init__(self): + super(EqualityPreservingStringifyMapper, self).__init__(constant_mapper=repr) + def map_constant(self, expr, enclosing_prec): if isinstance(expr, np.generic): # Explicitly typed: Emitted string must reflect type exactly. @@ -257,7 +257,7 @@ class EqualityPreservingStringifyMapper(StringifyMapperBase): return "%s(%s)" % (type(expr).__name__, repr(expr)) else: - return super(StringifyMapper, self).map_constant( + return super(EqualityPreservingStringifyMapper, self).map_constant( expr, enclosing_prec) -- GitLab From 55cf1cd5072d48e85bc66f31bcadc1381820008d Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 17 Aug 2019 19:38:02 -0500 Subject: [PATCH 7/8] Fix base class of TaggedVariable --- loopy/symbolic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loopy/symbolic.py b/loopy/symbolic.py index 379ea7142..8f5337533 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -520,7 +520,7 @@ class TypeCast(LoopyExpressionBase): mapper_method = intern("map_type_cast") -class TaggedVariable(LoopyExpressionBase): +class TaggedVariable(LoopyExpressionBase, p.Variable): """This is an identifier with a tag, 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 -- GitLab From 3e8ee71e499985a653917ac0e3e8a06e74a5d7d3 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 17 Aug 2019 20:24:08 -0500 Subject: [PATCH 8/8] Fix how CInstruction uses EqualityPreservingStringifyMapper --- loopy/kernel/instruction.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 5dee96e75..9b5c7a5b4 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -438,6 +438,16 @@ class InstructionBase(ImmutableRecord): # First sort the fields, as a canonical form items = tuple(sorted(field_value, key=str)) key_builder.update_for_pymbolic_field(field_name, items) + + # from CExpression + elif field_name == "iname_exprs": + from loopy.symbolic import EqualityPreservingStringifyMapper + key_builder.field_dict[field_name] = [ + (iname, EqualityPreservingStringifyMapper()(expr) + .encode("utf-8")) + for iname, expr in self.iname_exprs + ] + else: key_builder.update_for_field(field_name, field_value) @@ -1205,7 +1215,7 @@ class CInstruction(InstructionBase): fields = InstructionBase.fields | \ set("iname_exprs code read_variables assignees".split()) pymbolic_fields = InstructionBase.pymbolic_fields | \ - set("iname_exprs assignees".split()) + set("assignees".split()) pymbolic_set_fields = InstructionBase.pymbolic_set_fields | \ set(["read_variables"]) -- GitLab