From 350482752308b1639d29d130725776d1925e7782 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 15 Nov 2017 12:32:41 +0000 Subject: [PATCH 01/10] Plausibly handle typed literals Fixes #99. --- loopy/symbolic.py | 40 ++++++++++++++++++++++++++++ loopy/target/c/codegen/expression.py | 12 +++++++++ loopy/type_inference.py | 3 +++ test/test_target.py | 20 ++++++++++++++ 4 files changed, 75 insertions(+) diff --git a/loopy/symbolic.py b/loopy/symbolic.py index 543c2743b..f420912cf 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -147,6 +147,11 @@ class WalkMapper(WalkMapperBase): self.rec(expr.expr, *args) + def map_type_annotation(self, expr, *args): + if not self.visit(expr): + return + self.rec(expr.child, *args) + map_tagged_variable = WalkMapperBase.map_variable def map_loopy_function_identifier(self, expr, *args): @@ -219,6 +224,17 @@ class StringifyMapper(StringifyMapperBase): def map_rule_argument(self, expr, enclosing_prec): return "" % expr.index + def map_type_annotation(self, expr, enclosing_prec): + from pymbolic.mapper.stringifier import PREC_NONE + from loopy.types import NumpyType + typename = NumpyType(expr.type).dtype.name + return "(%s).astype(%s)" % (self.rec(expr.child, PREC_NONE), + typename) + + def map_type_cast(self, expr, enclosing_prec): + from pymbolic.mapper.stringifier import PREC_NONE + return "(%s)(%s)" % (expr.ctype, self.rec(expr.child, PREC_NONE)) + class UnidirectionalUnifier(UnidirectionalUnifierBase): def map_reduction(self, expr, other, unis): @@ -273,6 +289,9 @@ class DependencyMapper(DependencyMapperBase): map_linear_subscript = DependencyMapperBase.map_subscript + def map_type_annotation(self, expr): + return self.rec(expr.child) + class SubstitutionRuleExpander(IdentityMapper): def __init__(self, rules): @@ -406,9 +425,27 @@ 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): + def __init__(self, ctype, child): + super(TypeCast, self).__init__() + self.ctype = ctype + self.child = child + + def __getinitargs__(self): + return (self.ctype, self.child) + + def stringifier(self): + return StringifyMapper + + mapper_method = intern("map_type_cast") + + class TaggedVariable(p.Variable): """This is an identifier with a tag, such as 'matrix$one', where 'one' identifies this specific use of the identifier. This mechanism @@ -1562,6 +1599,9 @@ class BatchedAccessRangeMapper(WalkMapper): def map_reduction(self, expr, inames): return WalkMapper.map_reduction(self, expr, inames | set(expr.inames)) + def map_type_annotation(self, expr, inames): + return self.rec(expr.child, inames) + class AccessRangeMapper(object): diff --git a/loopy/target/c/codegen/expression.py b/loopy/target/c/codegen/expression.py index 5d269a8bc..8a1f0fbe0 100644 --- a/loopy/target/c/codegen/expression.py +++ b/loopy/target/c/codegen/expression.py @@ -340,6 +340,14 @@ class ExpressionToCExpressionMapper(IdentityMapper): expr.operator, self.rec(expr.right, inner_type_context)) + def map_type_annotation(self, expr, type_context): + from loopy.types import NumpyType + from loopy.symbolic import TypeCast + registry = self.codegen_state.ast_builder.target.get_dtype_registry() + dtype = NumpyType(expr.type) + return TypeCast(registry.dtype_to_ctype(dtype), + self.rec(expr.child, type_context)) + def map_constant(self, expr, type_context): if isinstance(expr, (complex, np.complexfloating)): try: @@ -701,6 +709,10 @@ class CExpressionToCodeMapper(RecursiveMapper): # }}} + def map_type_cast(self, expr, prec): + return "(%s)(%s)" % (expr.ctype, + self.rec(expr.child, PREC_NONE)) + def map_constant(self, expr, prec): return repr(expr) diff --git a/loopy/type_inference.py b/loopy/type_inference.py index 409cbbc5e..7d65cb813 100644 --- a/loopy/type_inference.py +++ b/loopy/type_inference.py @@ -237,6 +237,9 @@ class TypeInferenceMapper(CombineMapper): else: raise TypeInferenceFailure("Cannot deduce type of constant '%s'" % expr) + def map_type_annotation(self, expr): + return [NumpyType(expr.type)] + def map_subscript(self, expr): return self.rec(expr.aggregate) diff --git a/test/test_target.py b/test/test_target.py index ad0cb7439..f5b18e862 100644 --- a/test/test_target.py +++ b/test/test_target.py @@ -240,6 +240,26 @@ def test_numba_cuda_target(): print(lp.generate_code_v2(knl).all_code()) +def test_sized_integer_c_codegen(ctx_factory): + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + + from pymbolic import var + knl = lp.make_kernel( + "{[i]: 0<=i 1: exec(sys.argv[1]) -- GitLab From 97d768c4fab4052054f5a43084f055e0aec4bd96 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Thu, 16 Nov 2017 14:31:14 +0000 Subject: [PATCH 02/10] test: Find f90 file in test_numa_diff using __file__ This allows invocation of pytest from an arbitrary directory to succeed in running the test. --- test/test_numa_diff.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_numa_diff.py b/test/test_numa_diff.py index 0de08f5f6..eff3dbd0e 100644 --- a/test/test_numa_diff.py +++ b/test/test_numa_diff.py @@ -28,6 +28,7 @@ import pytest import loopy as lp import pyopencl as cl import sys +import os pytestmark = pytest.mark.importorskip("fparser") @@ -49,7 +50,7 @@ __all__ = [ def test_gnuma_horiz_kernel(ctx_factory, ilp_multiple, Nq, opt_level): # noqa ctx = ctx_factory() - filename = "strongVolumeKernels.f90" + filename = os.path.join(os.path.dirname(__file__), "strongVolumeKernels.f90") with open(filename, "r") as sourcef: source = sourcef.read() -- GitLab From 6414bf15a989537338704057956bd4979838ece1 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 21 Nov 2017 11:59:00 +0000 Subject: [PATCH 03/10] Remove unnecessary TypeCast node --- loopy/symbolic.py | 19 ------------------- loopy/target/c/codegen/expression.py | 9 ++------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/loopy/symbolic.py b/loopy/symbolic.py index f420912cf..e6a76befe 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -231,10 +231,6 @@ class StringifyMapper(StringifyMapperBase): return "(%s).astype(%s)" % (self.rec(expr.child, PREC_NONE), typename) - def map_type_cast(self, expr, enclosing_prec): - from pymbolic.mapper.stringifier import PREC_NONE - return "(%s)(%s)" % (expr.ctype, self.rec(expr.child, PREC_NONE)) - class UnidirectionalUnifier(UnidirectionalUnifierBase): def map_reduction(self, expr, other, unis): @@ -431,21 +427,6 @@ class TypeAnnotation(p.Expression): mapper_method = intern("map_type_annotation") -class TypeCast(p.Expression): - def __init__(self, ctype, child): - super(TypeCast, self).__init__() - self.ctype = ctype - self.child = child - - def __getinitargs__(self): - return (self.ctype, self.child) - - def stringifier(self): - return StringifyMapper - - mapper_method = intern("map_type_cast") - - class TaggedVariable(p.Variable): """This is an identifier with a tag, such as 'matrix$one', where 'one' identifies this specific use of the identifier. This mechanism diff --git a/loopy/target/c/codegen/expression.py b/loopy/target/c/codegen/expression.py index 8a1f0fbe0..410d6ee37 100644 --- a/loopy/target/c/codegen/expression.py +++ b/loopy/target/c/codegen/expression.py @@ -342,11 +342,10 @@ class ExpressionToCExpressionMapper(IdentityMapper): def map_type_annotation(self, expr, type_context): from loopy.types import NumpyType - from loopy.symbolic import TypeCast registry = self.codegen_state.ast_builder.target.get_dtype_registry() dtype = NumpyType(expr.type) - return TypeCast(registry.dtype_to_ctype(dtype), - self.rec(expr.child, type_context)) + cast = var("(%s)" % registry.dtype_to_ctype(dtype)) + return cast(self.rec(expr.child, type_context)) def map_constant(self, expr, type_context): if isinstance(expr, (complex, np.complexfloating)): @@ -709,10 +708,6 @@ class CExpressionToCodeMapper(RecursiveMapper): # }}} - def map_type_cast(self, expr, prec): - return "(%s)(%s)" % (expr.ctype, - self.rec(expr.child, PREC_NONE)) - def map_constant(self, expr, prec): return repr(expr) -- GitLab From db7b70502fd519d2112b6a5f5c089494f0faeaae Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 21 Nov 2017 14:34:14 +0000 Subject: [PATCH 04/10] Change string syntax to cast(typename, expr) --- loopy/symbolic.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/loopy/symbolic.py b/loopy/symbolic.py index e6a76befe..d00c8c8b0 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -228,8 +228,7 @@ class StringifyMapper(StringifyMapperBase): from pymbolic.mapper.stringifier import PREC_NONE from loopy.types import NumpyType typename = NumpyType(expr.type).dtype.name - return "(%s).astype(%s)" % (self.rec(expr.child, PREC_NONE), - typename) + return "cast(%s, %s)" % (typename, self.rec(expr.child, PREC_NONE)) class UnidirectionalUnifier(UnidirectionalUnifierBase): -- GitLab From 8ec348c51e8de4e3f4302968e3028e27ececbca7 Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 21 Nov 2017 14:34:28 +0000 Subject: [PATCH 05/10] Only allow casting to numeric types --- loopy/type_inference.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/loopy/type_inference.py b/loopy/type_inference.py index 7d65cb813..631043ce0 100644 --- a/loopy/type_inference.py +++ b/loopy/type_inference.py @@ -238,7 +238,11 @@ class TypeInferenceMapper(CombineMapper): raise TypeInferenceFailure("Cannot deduce type of constant '%s'" % expr) def map_type_annotation(self, expr): - return [NumpyType(expr.type)] + dtype = NumpyType(expr.type) + if not issubclass(dtype.dtype.type, np.number): + raise LoopyError("Type annotations only for numeric types, not '%s'" % + dtype) + return [dtype] def map_subscript(self, expr): return self.rec(expr.aggregate) -- GitLab From 8281fdb59ed574d13d7fbd832eaed970afffd0fe Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Tue, 21 Nov 2017 17:12:19 +0000 Subject: [PATCH 06/10] Check for invalid (non-numeric) cast in map_type_annotation Disallow casting a non-numeric subtype to a numeric type. --- loopy/type_inference.py | 3 +++ test/test_target.py | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/loopy/type_inference.py b/loopy/type_inference.py index 631043ce0..f631d01b4 100644 --- a/loopy/type_inference.py +++ b/loopy/type_inference.py @@ -242,6 +242,9 @@ class TypeInferenceMapper(CombineMapper): if not issubclass(dtype.dtype.type, np.number): raise LoopyError("Type annotations only for numeric types, not '%s'" % dtype) + subtype, = self.rec(expr.child) + if not issubclass(subtype.dtype.type, np.number): + raise LoopyError("Can't cast a '%s' to '%s'" % (subtype, dtype)) return [dtype] def map_subscript(self, expr): diff --git a/test/test_target.py b/test/test_target.py index f5b18e862..5a29a52a6 100644 --- a/test/test_target.py +++ b/test/test_target.py @@ -260,6 +260,19 @@ def test_sized_integer_c_codegen(ctx_factory): assert np.array_equal(a_ref, a.get()) +def test_invalid_type_annotation(): + from pymbolic import var + knl = lp.make_kernel( + "{[i]: 0<=i ctr = make_uint2(0, 0)", + lp.Assignment("a[i]", lp.symbolic.TypeAnnotation(np.int64, + var("ctr")) << var("i"))] + ) + + with pytest.raises(lp.LoopyError): + knl = lp.preprocess_kernel(knl) + + if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) -- GitLab From f61e9595caad306065597e06668b1dc89943525f Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 22 Nov 2017 01:29:38 -0600 Subject: [PATCH 07/10] Slightly improve expression docs --- doc/ref_kernel.rst | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/doc/ref_kernel.rst b/doc/ref_kernel.rst index 3f01b0764..07b7836d8 100644 --- a/doc/ref_kernel.rst +++ b/doc/ref_kernel.rst @@ -327,15 +327,25 @@ Expressions Loopy's expressions are a slight superset of the expressions supported by :mod:`pymbolic`. -* ``if`` -* ``elif`` (following an ``if``) -* ``else`` (following an ``if`` / ``elif``) +* ``if(cond, then, else_)`` + +* ``a[[ 8*i + j ]]``: Linear subscripts. + See :class:`loopy.symbolic.LinearSubscript`. + * ``reductions`` - * duplication of reduction inames + See :class:`loopy.symbolic.Reduction`. + * ``reduce`` vs ``simul_reduce`` + * complex-valued arithmetic + * tagging of array access and substitution rule use ("$") + See :class:`loopy.symbolic.TaggedVariable`. + * ``indexof``, ``indexof_vec`` +* ``cast(type, value)``: No parse syntax currently. + See :class:`loopy.symbolic.TypeCast`. + TODO: Functions TODO: Reductions -- GitLab From 29aa81ee15e0e085777535afb62a8249cefaacad Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 22 Nov 2017 01:30:32 -0600 Subject: [PATCH 08/10] Revive type casts, use for typed constants --- loopy/__init__.py | 4 +- loopy/symbolic.py | 63 ++++++++++++++++++++++++---- loopy/target/c/codegen/expression.py | 6 +-- loopy/type_inference.py | 10 ++--- test/test_target.py | 5 +-- 5 files changed, 63 insertions(+), 25 deletions(-) diff --git a/loopy/__init__.py b/loopy/__init__.py index e48a8b3bd..7a853d115 100644 --- a/loopy/__init__.py +++ b/loopy/__init__.py @@ -27,7 +27,7 @@ import six from six.moves import range, zip from loopy.symbolic import ( - TaggedVariable, Reduction, LinearSubscript, ) + TaggedVariable, Reduction, LinearSubscript, TypeCast) from loopy.diagnostic import LoopyError, LoopyWarning @@ -145,7 +145,7 @@ from loopy.target.numba import NumbaTarget, NumbaCudaTarget __all__ = [ - "TaggedVariable", "Reduction", "LinearSubscript", + "TaggedVariable", "Reduction", "LinearSubscript", "TypeCast", "auto", diff --git a/loopy/symbolic.py b/loopy/symbolic.py index d00c8c8b0..785ea0c51 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -103,8 +103,8 @@ class IdentityMapperMixin(object): # leaf, doesn't change return expr - def map_type_annotation(self, expr, *args): - return TypeAnnotation(expr.type, self.rec(expr.child)) + def map_type_cast(self, expr, *args): + return type(expr)(expr.type, self.rec(expr.child)) map_linear_subscript = IdentityMapperBase.map_subscript @@ -147,7 +147,7 @@ class WalkMapper(WalkMapperBase): self.rec(expr.expr, *args) - def map_type_annotation(self, expr, *args): + def map_type_cast(self, expr, *args): if not self.visit(expr): return self.rec(expr.child, *args) @@ -224,11 +224,9 @@ class StringifyMapper(StringifyMapperBase): def map_rule_argument(self, expr, enclosing_prec): return "" % expr.index - def map_type_annotation(self, expr, enclosing_prec): + def map_type_cast(self, expr, enclosing_prec): from pymbolic.mapper.stringifier import PREC_NONE - from loopy.types import NumpyType - typename = NumpyType(expr.type).dtype.name - return "cast(%s, %s)" % (typename, self.rec(expr.child, PREC_NONE)) + return "cast(%s, %s)" % (repr(expr.type), self.rec(expr.child, PREC_NONE)) class UnidirectionalUnifier(UnidirectionalUnifierBase): @@ -284,7 +282,7 @@ class DependencyMapper(DependencyMapperBase): map_linear_subscript = DependencyMapperBase.map_subscript - def map_type_annotation(self, expr): + def map_type_cast(self, expr): return self.rec(expr.child) @@ -412,6 +410,10 @@ class TypedCSE(p.CommonSubexpression): class TypeAnnotation(p.Expression): + """Undocumented for now. Currently only used internally around LHSs of + assignemnts that create temporaries. + """ + def __init__(self, type, child): super(TypeAnnotation, self).__init__() self.type = type @@ -426,6 +428,49 @@ class TypeAnnotation(p.Expression): mapper_method = intern("map_type_annotation") +class TypeCast(p.Expression): + """Only defined for numerical types with semantics matching + :meth:`numpy.ndarray.astype`. + + .. attribute:: child + + The expression to be cast. + """ + + def __init__(self, type, child): + super(TypeCast, self).__init__() + + from loopy.types import to_loopy_type, NumpyType + type = to_loopy_type(type) + + if (not isinstance(type, NumpyType) + or not issubclass(type.dtype.type, np.number)): + from loopy.diagnostic import LoopyError + raise LoopyError("TypeCast only supports numerical numpy types, " + "not ''" % type) + + # We're storing the type as a name for now to avoid + # numpy pickling bug madness. (see loopy.types) + self._type_name = type.dtype.name + self.child = child + + @property + def type(self): + from loopy.types import NumpyType + return NumpyType(np.dtype(self._type_name)) + + # init_arg_names is a misnomer--they're attribute names used for pickling. + init_arg_names = ("_type_name", "child") + + def __getinitargs__(self): + return (self._type_name, self.child) + + def stringifier(self): + return StringifyMapper + + mapper_method = intern("map_type_cast") + + class TaggedVariable(p.Variable): """This is an identifier with a tag, such as 'matrix$one', where 'one' identifies this specific use of the identifier. This mechanism @@ -1579,7 +1624,7 @@ class BatchedAccessRangeMapper(WalkMapper): def map_reduction(self, expr, inames): return WalkMapper.map_reduction(self, expr, inames | set(expr.inames)) - def map_type_annotation(self, expr, inames): + def map_type_cast(self, expr, inames): return self.rec(expr.child, inames) diff --git a/loopy/target/c/codegen/expression.py b/loopy/target/c/codegen/expression.py index 410d6ee37..caee73eb1 100644 --- a/loopy/target/c/codegen/expression.py +++ b/loopy/target/c/codegen/expression.py @@ -340,11 +340,9 @@ class ExpressionToCExpressionMapper(IdentityMapper): expr.operator, self.rec(expr.right, inner_type_context)) - def map_type_annotation(self, expr, type_context): - from loopy.types import NumpyType + def map_type_cast(self, expr, type_context): registry = self.codegen_state.ast_builder.target.get_dtype_registry() - dtype = NumpyType(expr.type) - cast = var("(%s)" % registry.dtype_to_ctype(dtype)) + cast = var("(%s)" % registry.dtype_to_ctype(expr.type)) return cast(self.rec(expr.child, type_context)) def map_constant(self, expr, type_context): diff --git a/loopy/type_inference.py b/loopy/type_inference.py index f631d01b4..6ffc1dff5 100644 --- a/loopy/type_inference.py +++ b/loopy/type_inference.py @@ -237,15 +237,11 @@ class TypeInferenceMapper(CombineMapper): else: raise TypeInferenceFailure("Cannot deduce type of constant '%s'" % expr) - def map_type_annotation(self, expr): - dtype = NumpyType(expr.type) - if not issubclass(dtype.dtype.type, np.number): - raise LoopyError("Type annotations only for numeric types, not '%s'" % - dtype) + def map_type_cast(self, expr): subtype, = self.rec(expr.child) if not issubclass(subtype.dtype.type, np.number): - raise LoopyError("Can't cast a '%s' to '%s'" % (subtype, dtype)) - return [dtype] + raise LoopyError("Can't cast a '%s' to '%s'" % (subtype, expr.type)) + return [expr.type] def map_subscript(self, expr): return self.rec(expr.aggregate) diff --git a/test/test_target.py b/test/test_target.py index 5a29a52a6..121d26e0e 100644 --- a/test/test_target.py +++ b/test/test_target.py @@ -247,7 +247,7 @@ def test_sized_integer_c_codegen(ctx_factory): from pymbolic import var knl = lp.make_kernel( "{[i]: 0<=i ctr = make_uint2(0, 0)", - lp.Assignment("a[i]", lp.symbolic.TypeAnnotation(np.int64, - var("ctr")) << var("i"))] + lp.Assignment("a[i]", lp.TypeCast(np.int64, var("ctr")) << var("i"))] ) with pytest.raises(lp.LoopyError): -- GitLab From af1775e869702f86a7476a6a1797131c7a2b337e Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 22 Nov 2017 01:56:19 -0600 Subject: [PATCH 09/10] Fix IdentityMapper for type annotation --- loopy/symbolic.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/loopy/symbolic.py b/loopy/symbolic.py index 785ea0c51..01195c858 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -103,9 +103,11 @@ class IdentityMapperMixin(object): # leaf, doesn't change return expr - def map_type_cast(self, expr, *args): + def map_type_annotation(self, expr, *args): return type(expr)(expr.type, self.rec(expr.child)) + map_type_cast = map_type_annotation + map_linear_subscript = IdentityMapperBase.map_subscript map_rule_argument = map_group_hw_index -- GitLab From 153c766db03fd9cd7e24ea97d9b6e15570faf3da Mon Sep 17 00:00:00 2001 From: Lawrence Mitchell Date: Wed, 22 Nov 2017 10:26:41 +0000 Subject: [PATCH 10/10] Fix format string in TypeCast.__init__ Add test that casting to an invalid type raises LoopyError. --- loopy/symbolic.py | 4 ++-- test/test_target.py | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/loopy/symbolic.py b/loopy/symbolic.py index 01195c858..2d31c63ef 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -413,7 +413,7 @@ class TypedCSE(p.CommonSubexpression): class TypeAnnotation(p.Expression): """Undocumented for now. Currently only used internally around LHSs of - assignemnts that create temporaries. + assignments that create temporaries. """ def __init__(self, type, child): @@ -449,7 +449,7 @@ class TypeCast(p.Expression): or not issubclass(type.dtype.type, np.number)): from loopy.diagnostic import LoopyError raise LoopyError("TypeCast only supports numerical numpy types, " - "not ''" % type) + "not '%s'" % type) # We're storing the type as a name for now to avoid # numpy pickling bug madness. (see loopy.types) diff --git a/test/test_target.py b/test/test_target.py index 121d26e0e..01a2e5d9d 100644 --- a/test/test_target.py +++ b/test/test_target.py @@ -260,7 +260,7 @@ def test_sized_integer_c_codegen(ctx_factory): assert np.array_equal(a_ref, a.get()) -def test_invalid_type_annotation(): +def test_child_invalid_type_cast(): from pymbolic import var knl = lp.make_kernel( "{[i]: 0<=i 1: exec(sys.argv[1]) -- GitLab