diff --git a/doc/ref_kernel.rst b/doc/ref_kernel.rst index 46063310917fe4c111bac96a5a15c7c588974617..409cbef576d654be973dd6d1424ac40d3ea60982 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/kernel/instruction.py b/loopy/kernel/instruction.py index 5dee96e75d36eace37efd1ea1fcaa98cfef7d0ec..9b5c7a5b421c8e37e4ddac88b7bf98fa59e32d6e 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"]) diff --git a/loopy/symbolic.py b/loopy/symbolic.py index 6aec1c995c18887455c3c37420c102298bbe6bd9..8f5337533fd9e96c77b56154c1848f5ac419b425 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -235,6 +235,32 @@ 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 __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. + + # FIXME: This syntax cannot currently be parsed. + + return "%s(%s)" % (type(expr).__name__, repr(expr)) + else: + return super(EqualityPreservingStringifyMapper, self).map_constant( + expr, enclosing_prec) + + class UnidirectionalUnifier(UnidirectionalUnifierBase): def map_reduction(self, expr, other, unis): if not isinstance(other, type(expr)): @@ -331,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:: @@ -344,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,) @@ -355,7 +387,7 @@ class Literal(p.Leaf): mapper_method = "map_literal" -class ArrayLiteral(p.Leaf): +class ArrayLiteral(LoopyExpressionBase): """An array literal. .. note:: @@ -368,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,) @@ -379,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,) @@ -414,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`. """ @@ -441,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. """ @@ -454,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`. @@ -497,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, 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 @@ -519,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`. @@ -612,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 @@ -627,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. """ @@ -641,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. @@ -661,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/loopy/target/c/codegen/expression.py b/loopy/target/c/codegen/expression.py index 8ef921e447bf10d85ac60460f904d528ac64da19..a6742c225cce42bee5f48d44662f0e515d14b556 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,36 @@ 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. + + # 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)) + + # Disabled for now, possibly should be a subtarget. + # 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": diff --git a/loopy/tools.py b/loopy/tools.py index 0fc6d1bf9b3885db86cc1f4642a4e1342fcfd5a0..33b6616f32fb6c5fa6e4517e137ef426a806fb3f 100644 --- a/loopy/tools.py +++ b/loopy/tools.py @@ -161,7 +161,9 @@ 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 EqualityPreservingStringifyMapper + self.field_dict[field_name] = \ + EqualityPreservingStringifyMapper()(value).encode("utf-8") def key(self): """A key suitable for equality comparison.""" diff --git a/loopy/version.py b/loopy/version.py index 62940e8a7576ffdaa5e85ba545cd95e602dfcbea..66c7a0cf46f3b40a8cc0af345d55c40e4e7d7f4f 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 diff --git a/setup.py b/setup.py index 4229aeb45084cb30b0b8942d0c05d27a1aab2144..ed19e05840db935031fdad11dd3af2dc389fd59f 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",