From ae724eb29626ad35f094a844debed783ec4bced3 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Wed, 5 May 2021 11:49:09 -0500 Subject: [PATCH 01/21] Adds builtins for BDF/NDF use in Leap --- dagrt/builtins_python.py | 63 ++++++++++++++++++ dagrt/function_registry.py | 128 +++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) diff --git a/dagrt/builtins_python.py b/dagrt/builtins_python.py index 623776c..278ba48 100644 --- a/dagrt/builtins_python.py +++ b/dagrt/builtins_python.py @@ -47,6 +47,25 @@ def builtin_norm_2(x): return np.linalg.norm(x, 2) +def builtin_norm_wrms(x, y, ynew, atol, rtol): + import numpy as np + if np.isscalar(x): + return abs(x) + # Use y to calculate weights. + w = np.zeros(len(y)) + for i in range(len(y)): + if np.isscalar(rtol): + w[i] = 1.0/(rtol*abs(ynew[i]) + atol) + else: + w[i] = 1.0/(rtol[i]*abs(ynew[i]) + atol) + # now calculate the wrms norm + add = 0 + for i in range(len(x)): + add += (w[i]*x[i])**2 + + return np.sqrt(add/len(y)) + + def builtin_norm_inf(x): import numpy as np if np.isscalar(x): @@ -59,6 +78,11 @@ def builtin_dot_product(a, b): return np.vdot(a, b) +def builtin_cumulative_product(a, axis): + import numpy as np + return np.cumprod(a, axis=axis) + + def builtin_array(n): import numpy as np if n != np.floor(n): @@ -68,6 +92,16 @@ def builtin_array(n): return np.empty(n, dtype=np.float64) +def builtin_array_utype(n, x): + import numpy as np + if n != np.floor(n): + raise ValueError("array() argument n is not an integer") + n = int(n) + + return np.empty((n, x.size), dtype=x.dtype) + #return np.zeros((n, x.size), dtype=x.dtype) + + def builtin_matmul(a, b, a_cols, b_cols): import numpy as np if a_cols != np.floor(a_cols): @@ -85,6 +119,30 @@ def builtin_matmul(a, b, a_cols, b_cols): return res_mat.reshape(-1, order="F") +def builtin_reshape(a, a_cols): + return a.reshape(-1, a_cols, order="F") + + +def builtin_user_matmul(a, b, a_cols, b_cols, c_cols): + import numpy as np + if a_cols != np.floor(a_cols): + raise ValueError("matmul() argument a_cols is not an integer") + if b_cols != np.floor(b_cols): + raise ValueError("matmul() argument b_cols is not an integer") + if c_cols != np.floor(c_cols): + raise ValueError("matmul() argument b_cols is not an integer") + a_cols = int(a_cols) + b_cols = int(b_cols) + c_cols = int(c_cols) + + a_mat = a.reshape(-1, a_cols, order="F") + b_mat = b.reshape(-1, b_cols, order="F") + + res_mat = a_mat.dot(b_mat) + + return res_mat.reshape(-1, c_cols, order="F") + + def builtin_transpose(a, a_cols): import numpy as np if a_cols != np.floor(a_cols): @@ -139,10 +197,15 @@ def builtin_print(arg): "isnan": builtin_isnan, "norm_1": builtin_norm_1, "norm_2": builtin_norm_2, + "norm_wrms": builtin_norm_wrms, "norm_inf": builtin_norm_inf, "dot_product": builtin_dot_product, + "cumulative_product": builtin_cumulative_product, "array": builtin_array, + "array_utype": builtin_array_utype, "matmul": builtin_matmul, + "user_matmul": builtin_user_matmul, + "reshape": builtin_reshape, "transpose": builtin_transpose, "linear_solve": builtin_linear_solve, "svd": builtin_svd, diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 409fd90..a638fb5 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -293,6 +293,27 @@ class Norm2(_NormBase): identifier = "norm_2" +class NormWRMS(_NormBase): + """``norm_wrms(x)`` returns the wrms-norm of *x*. + *x* is a user type or array, and *y* is also a user type + or array. + """ + identifier = "norm_wrms" + arg_names = ("x", "y", "ynew", "atol", "rtol") + default_dict = {} + + def get_result_kinds(self, arg_kinds, check): + x_kind, y_kind, atol_kind, rtol_kind = self.resolve_args(arg_kinds) + + if check and not isinstance(x_kind, (NoneType, Array, UserType)): + raise TypeError("argument 'x' of 'norm' is not a user type") + + if check and not isinstance(y_kind, (NoneType, Array, UserType)): + raise TypeError("argument 'y' of 'norm' is not a user type") + + return (Scalar(is_real_valued=True),) + + class NormInf(_NormBase): """``norm_inf(x)`` returns the infinity-norm of *x*. *x* is a user type or array. @@ -323,6 +344,28 @@ def get_result_kinds(self, arg_kinds, check): return (Scalar(is_real_valued=False),) +class CumulativeProduct(Function): + """``cumulative_product(x, a)`` return the cumulative product of *x*. + *x* is an array or user type, and *a* is an integer indicating the + axis over which the cumulative product should be taken. + """ + + result_names = ("result",) + identifier = "cumulative_product" + arg_names = ("x", "a") + default_dict = {} + + def get_result_kinds(self, arg_kinds, check): + x_kind, a_kind = self.resolve_args(arg_kinds) + + if check and not isinstance(x_kind, (NoneType, Array, UserType)): + raise TypeError("argument 'x' of 'cumulative_product' is not user type") + if check and not isinstance(a_kind, (NoneType, Scalar)): + raise TypeError("argument 'y' of 'cumulative_product' is not a scalar") + + return (Scalar(is_real_valued=False),) + + class Len(Function): """``len(x)`` returns the number of degrees of freedom in *x*. *x* is a user type or array. @@ -380,6 +423,27 @@ def get_result_kinds(self, arg_kinds, check): return (Array(is_real_valued=True),) +class ArrayUType_(Function): # noqa + """``array_utype(n, x)`` returns an empty array with *n* entries in it. + *n* must be an integer. + """ + + result_names = ("result",) + identifier = "array_utype" + arg_names = ("n", "x") + default_dict = {} + + def get_result_kinds(self, arg_kinds, check): + n_kind, x_kind = self.resolve_args(arg_kinds) + + if check and not isinstance(n_kind, Scalar): + raise TypeError("argument 'n' of 'array_utype' is not a scalar") + if check and not isinstance(x_kind, (NoneType, Array, UserType)): + raise TypeError("argument 'x' of 'array_utype' is not a user type") + + return (Array(),) + + class MatMul(Function): """``matmul(a, b, a_cols, b_cols)`` returns a 1D array containing the matrix resulting from multiplying the arrays *a* and *b* (both interpreted @@ -412,6 +476,41 @@ def get_result_kinds(self, arg_kinds, check): return (Array(is_real_valued),) +class UserMatMul(Function): + """``matmul(a, b, a_cols, b_cols, c_cols)`` returns a 1D array containing the + matrix resulting from multiplying the arrays *a* and *b* (both interpreted + as matrices, with a number of columns *a_cols* and *b_cols* respectively). + """ + + result_names = ("result",) + identifier = "user_matmul" + arg_names = ("a", "b", "a_cols", "b_cols", "c_cols") + default_dict = {} + + def get_result_kinds(self, arg_kinds, check): + [a_kind, b_kind, a_cols_kind, + b_cols_kind, c_cols_kind] = self.resolve_args(arg_kinds) + + if a_kind is None or b_kind is None: + raise UnableToInferKind( + "matmul needs to know both arguments to infer result kind") + + if check and not isinstance(a_kind, Array): + raise TypeError("argument 'a' of 'matmul' is not an array") + if check and not isinstance(b_kind, Array): + raise TypeError("argument 'a' of 'matmul' is not an array") + if check and not isinstance(a_cols_kind, Scalar): + raise TypeError("argument 'a_cols' of 'matmul' is not a scalar") + if check and not isinstance(b_cols_kind, Scalar): + raise TypeError("argument 'b_cols' of 'matmul' is not a scalar") + if check and not isinstance(c_cols_kind, Scalar): + raise TypeError("argument 'c_cols' of 'matmul' is not a scalar") + + is_real_valued = a_kind.is_real_valued and b_kind.is_real_valued + + return (Array(is_real_valued),) + + class Transpose(Function): """``transpose(a, a_cols)`` returns a 1D array containing the matrix resulting from transposing the array *a* (interpreted @@ -440,6 +539,30 @@ def get_result_kinds(self, arg_kinds, check): return (Array(is_real_valued),) +class Reshape(Function): + + result_names = ("result",) + identifier = "reshape" + arg_names = ("a", "a_cols") + default_dict = {} + + def get_result_kinds(self, arg_kinds, check): + a_kind, a_cols_kind = self.resolve_args(arg_kinds) + + if a_kind is None: + raise UnableToInferKind( + "transpose needs to know both arguments to infer result kind") + + if check and not isinstance(a_kind, Array): + raise TypeError("argument 'a' of 'transpose' is not an array") + if check and not isinstance(a_cols_kind, Scalar): + raise TypeError("argument 'a_cols' of 'transpose' is not a scalar") + + is_real_valued = a_kind.is_real_valued + + return (Array(is_real_valued),) + + class LinearSolve(Function): """``linear_solve(a, b, a_cols, b_cols)`` returns a 1D array containing the matrix resulting from multiplying the matrix inverse of *a* by *b*, both @@ -538,13 +661,18 @@ def _make_bfr(): for func, py_pattern in [ (Norm1(), "self._builtin_norm_1({args})"), (Norm2(), "self._builtin_norm_2({args})"), + (NormWRMS(), "self._builtin_norm_wrms({args})"), (NormInf(), "self._builtin_norm_inf({args})"), (DotProduct(), "{numpy}.vdot({args})"), + (CumulativeProduct(), "{numpy}.cumprod({args})"), (Len(), "{numpy}.size({args})"), (IsNaN(), "{numpy}.isnan({args})"), (Array_(), "self._builtin_array({args})"), + (ArrayUType_(), "self._builtin_array_utype({args})"), (MatMul(), "self._builtin_matmul({args})"), + (UserMatMul(), "self._builtin_user_matmul({args})"), (Transpose(), "self._builtin_transpose({args})"), + (Reshape(), "self._builtin_reshape({args})"), (LinearSolve(), "self._builtin_linear_solve({args})"), (Print(), "self._builtin_print({args})"), (SVD(), "self._builtin_svd({args})"), From ce489e9a58fd6e7801195643edcf915300ac1c82 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Tue, 11 May 2021 09:34:07 -0500 Subject: [PATCH 02/21] Silence pylint --- dagrt/function_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index a638fb5..019c8da 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -441,7 +441,7 @@ def get_result_kinds(self, arg_kinds, check): if check and not isinstance(x_kind, (NoneType, Array, UserType)): raise TypeError("argument 'x' of 'array_utype' is not a user type") - return (Array(),) + return (Array(is_real_valued=True),) class MatMul(Function): From 562b2b5b05bcae586ba38bd700288db1383b7aa1 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Wed, 12 May 2021 17:02:14 -0500 Subject: [PATCH 03/21] Removes no longer needed (and problematic) reshape and cumulative product builtins --- dagrt/builtins_python.py | 15 ++--------- dagrt/function_registry.py | 51 +++----------------------------------- 2 files changed, 5 insertions(+), 61 deletions(-) diff --git a/dagrt/builtins_python.py b/dagrt/builtins_python.py index 278ba48..2b9f582 100644 --- a/dagrt/builtins_python.py +++ b/dagrt/builtins_python.py @@ -78,11 +78,6 @@ def builtin_dot_product(a, b): return np.vdot(a, b) -def builtin_cumulative_product(a, axis): - import numpy as np - return np.cumprod(a, axis=axis) - - def builtin_array(n): import numpy as np if n != np.floor(n): @@ -98,8 +93,8 @@ def builtin_array_utype(n, x): raise ValueError("array() argument n is not an integer") n = int(n) - return np.empty((n, x.size), dtype=x.dtype) - #return np.zeros((n, x.size), dtype=x.dtype) + #return np.empty((n, x.size), dtype=x.dtype) + return np.zeros((n, x.size), dtype=x.dtype) def builtin_matmul(a, b, a_cols, b_cols): @@ -119,10 +114,6 @@ def builtin_matmul(a, b, a_cols, b_cols): return res_mat.reshape(-1, order="F") -def builtin_reshape(a, a_cols): - return a.reshape(-1, a_cols, order="F") - - def builtin_user_matmul(a, b, a_cols, b_cols, c_cols): import numpy as np if a_cols != np.floor(a_cols): @@ -200,12 +191,10 @@ def builtin_print(arg): "norm_wrms": builtin_norm_wrms, "norm_inf": builtin_norm_inf, "dot_product": builtin_dot_product, - "cumulative_product": builtin_cumulative_product, "array": builtin_array, "array_utype": builtin_array_utype, "matmul": builtin_matmul, "user_matmul": builtin_user_matmul, - "reshape": builtin_reshape, "transpose": builtin_transpose, "linear_solve": builtin_linear_solve, "svd": builtin_svd, diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 019c8da..278ac1b 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -59,12 +59,15 @@ .. autoclass:: Norm1 .. autoclass:: Norm2 +.. autoclass:: NormWRMS .. autoclass:: NormInf .. autoclass:: DotProduct .. autoclass:: Len .. autoclass:: IsNaN .. autoclass:: Array_ +.. autoclass:: ArrayUType_ .. autoclass:: MatMul +.. autoclass:: UserMatMul .. autoclass:: Transpose .. autoclass:: LinearSolve .. autoclass:: SVD @@ -344,28 +347,6 @@ def get_result_kinds(self, arg_kinds, check): return (Scalar(is_real_valued=False),) -class CumulativeProduct(Function): - """``cumulative_product(x, a)`` return the cumulative product of *x*. - *x* is an array or user type, and *a* is an integer indicating the - axis over which the cumulative product should be taken. - """ - - result_names = ("result",) - identifier = "cumulative_product" - arg_names = ("x", "a") - default_dict = {} - - def get_result_kinds(self, arg_kinds, check): - x_kind, a_kind = self.resolve_args(arg_kinds) - - if check and not isinstance(x_kind, (NoneType, Array, UserType)): - raise TypeError("argument 'x' of 'cumulative_product' is not user type") - if check and not isinstance(a_kind, (NoneType, Scalar)): - raise TypeError("argument 'y' of 'cumulative_product' is not a scalar") - - return (Scalar(is_real_valued=False),) - - class Len(Function): """``len(x)`` returns the number of degrees of freedom in *x*. *x* is a user type or array. @@ -539,30 +520,6 @@ def get_result_kinds(self, arg_kinds, check): return (Array(is_real_valued),) -class Reshape(Function): - - result_names = ("result",) - identifier = "reshape" - arg_names = ("a", "a_cols") - default_dict = {} - - def get_result_kinds(self, arg_kinds, check): - a_kind, a_cols_kind = self.resolve_args(arg_kinds) - - if a_kind is None: - raise UnableToInferKind( - "transpose needs to know both arguments to infer result kind") - - if check and not isinstance(a_kind, Array): - raise TypeError("argument 'a' of 'transpose' is not an array") - if check and not isinstance(a_cols_kind, Scalar): - raise TypeError("argument 'a_cols' of 'transpose' is not a scalar") - - is_real_valued = a_kind.is_real_valued - - return (Array(is_real_valued),) - - class LinearSolve(Function): """``linear_solve(a, b, a_cols, b_cols)`` returns a 1D array containing the matrix resulting from multiplying the matrix inverse of *a* by *b*, both @@ -664,7 +621,6 @@ def _make_bfr(): (NormWRMS(), "self._builtin_norm_wrms({args})"), (NormInf(), "self._builtin_norm_inf({args})"), (DotProduct(), "{numpy}.vdot({args})"), - (CumulativeProduct(), "{numpy}.cumprod({args})"), (Len(), "{numpy}.size({args})"), (IsNaN(), "{numpy}.isnan({args})"), (Array_(), "self._builtin_array({args})"), @@ -672,7 +628,6 @@ def _make_bfr(): (MatMul(), "self._builtin_matmul({args})"), (UserMatMul(), "self._builtin_user_matmul({args})"), (Transpose(), "self._builtin_transpose({args})"), - (Reshape(), "self._builtin_reshape({args})"), (LinearSolve(), "self._builtin_linear_solve({args})"), (Print(), "self._builtin_print({args})"), (SVD(), "self._builtin_svd({args})"), From 7319deadc853857d53fe1a53ea2b8db64b9dc4d1 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Wed, 26 May 2021 11:45:33 -0500 Subject: [PATCH 04/21] Aggressively work-in-progress Fortran UserTypeArray support --- dagrt/codegen/fortran.py | 226 +++++++++++++++++++++++++++++++++++-- dagrt/data.py | 34 ++++-- dagrt/function_registry.py | 49 +++++--- 3 files changed, 272 insertions(+), 37 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index 41c7490..c2dffa5 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -29,7 +29,7 @@ from dagrt.codegen.expressions import FortranExpressionMapper from dagrt.codegen.codegen_base import StructuredCodeGenerator from dagrt.utils import is_state_variable -from dagrt.data import UserType +from dagrt.data import UserType, UserTypeArray from pytools.py_codegen import ( # It's the same code. So sue me. PythonCodeGenerator as FortranEmitterBase) @@ -308,9 +308,17 @@ def find_sym_kind(self, expr): def transform(self, expr): raise NotImplementedError + def transform_utype_array(self, expr): + raise NotImplementedError + def map_variable(self, expr): if isinstance(self.find_sym_kind(expr), UserType): return self.transform(expr) + # We also need to catch UserTypeArrays here, and operate + # on their elements. + elif isinstance(self.find_sym_kind(expr), UserTypeArray): + expr = self.transform_utype_array(expr) + return self.transform(expr) else: return expr @@ -335,6 +343,9 @@ def __init__(self, code_generator, subscript): def transform(self, expr): return expr[self.subscript] + def transform_utype_array(self, expr): + return expr.attr("usertype_array_element") + # }}} @@ -1267,6 +1278,27 @@ def begin_emit(self, dag): # }}} + # {{{ for usertype arrays, should they be required + + with FortranTypeEmitter( + self.emitter, + "dagrt_usertype_array", + self) as emit: + + # FIXME: need to steal UserType identifier somehow. + self.emit_variable_decl( + self.name_manager.name_global('usertype_array_element'), + sym_kind=UserType('y'), is_argument=False, + refcount_name=self.name_manager.name_refcount( + 'uae', qualified_with_state=False)) + # Store the length of the UserTypeArray in the type itself. + from dagrt.data import Scalar + self.emit_variable_decl( + "array_size", sym_kind=Scalar(is_real_valued=True), + is_argument=False) + + # }}} + # {{{ state type with FortranTypeEmitter( @@ -1440,6 +1472,18 @@ def get_fortran_type_for_user_type(self, type_identifier, is_argument=False): return ftype + def get_fortran_type_for_user_type_array(self, type_identifier, + is_argument=False): + # The type is a structure type, which has two members: + # a UserType and the (integer) refcount. + ftype = StructureType("dagrt_usertype_array", ( + ("usertype_array_element", + self.get_fortran_type_for_user_type(type_identifier)), + (self.name_manager.name_refcount('uae', qualified_with_state=False), + PointerType(BuiltinType("integer"))))) + + return ftype + def emit_variable_decl(self, fortran_name, sym_kind, is_argument=False, other_specifiers=(), emit=None, refcount_name=None): @@ -1450,7 +1494,7 @@ def emit_variable_decl(self, fortran_name, sym_kind, type_specifiers = other_specifiers - from dagrt.data import UserType + from dagrt.data import UserType, UserTypeArray if isinstance(sym_kind, Boolean): type_name = "logical" @@ -1484,6 +1528,14 @@ def emit_variable_decl(self, fortran_name, sym_kind, "integer, pointer :: {refcount_name}".format( refcount_name=refcount_name)) + elif isinstance(sym_kind, UserTypeArray): + ftype = self.get_fortran_type_for_user_type_array(sym_kind.identifier, + is_argument=is_argument) + + # This is a 1D array of UserTypes. + type_name = "type(dagrt_usertype_array)" + type_specifiers += ("dimension(:)",) + else: raise ValueError("unknown variable kind: %s" % type(sym_kind).__name__) @@ -1561,11 +1613,25 @@ def emit_user_type_move(self, assignee_sym, assignee_fortran_name, sym_kind, expr): self.emit_variable_deinit(assignee_sym, sym_kind) + # We need to check if the RHS of the user type move is an + # element of a UserTypeArray - if so, we need to add the + # entrance to the derived type. + expression = self.name_manager[expr.name] + if isinstance(expr, Variable): + expr_kind = self.sym_kind_table.get(self.current_function, expr.name) + if isinstance(expr_kind, UserTypeArray): + expression = self.expr(expr) + "%usertype_array_element" + if isinstance(expr, (Subscript, Lookup)): + expr_kind = self.sym_kind_table.get(self.current_function, + expr.aggregate.name) + if isinstance(expr_kind, UserTypeArray): + expression = self.expr(expr) + "%usertype_array_element" + self.emit_traceable( "{name} => {expr}" .format( name=assignee_fortran_name, - expr=self.name_manager[expr.name])) + expr=expression)) self.emit_traceable( "{tgt_refcnt} => {refcnt}" .format( @@ -1587,11 +1653,16 @@ def emit_assign_expr_inner(self, else: subscript_str = "" + # Special treatment for usertype arrays needed here, + # since usertype arrays are actually intermediate structures. + if isinstance(sym_kind, UserTypeArray): + if assignee_subscript: + subscript_str += '%usertype_array_element' + if isinstance(expr, (Call, CallWithKwargs)): # These are supposed to have been transformed to AssignFunctionCall. raise RuntimeError("bare Call/CallWithKwargs encountered in " "Fortran code generator") - else: self.emit_trace("{assignee_fortran_name}{subscript_str} = {expr}..." .format( @@ -1600,18 +1671,58 @@ def emit_assign_expr_inner(self, expr=str(expr)[:50])) from dagrt.data import UserType - if not isinstance(sym_kind, UserType): + if isinstance(sym_kind, UserType): + ftype = self.get_fortran_type_for_user_type(sym_kind.identifier) + AssignmentEmitter(self)( + ftype, assignee_fortran_name, {}, expr, + is_rhs_target=True) + elif isinstance(sym_kind, UserTypeArray): + # This will require a specialized assignment emitter, + # wherein the RHS expression is transformed to insert + # the proper structure stuff for UserTypeArrays. + ftype = self.get_fortran_type_for_user_type_array( + sym_kind.identifier) + expression = self.expr(expr) + if isinstance(expr, Variable): + expr_kind = self.sym_kind_table.get(self.current_function, + expr.name) + if isinstance(expr_kind, UserTypeArray): + # We need to loop through the UserTypeArray here, + # and attach the structure entrance to both the LHS and + # the RHS... + ident = self.name_manager["i"] + expression = self.expr(expr) + \ + "({})%usertype_array_element".format(ident) + subscript_str += "({})%usertype_array_element".format(ident) + em = FortranDoEmitter( + self.emitter, ident, + "1, {}".format( + self.expr(expr) + "%array_size"), + self) + em.__enter__() + + if isinstance(expr, (Subscript, Lookup)): + # We're indexing into a UserTypeArray here. + expr_kind = self.sym_kind_table.get(self.current_function, + expr.aggregate.name) + if isinstance(expr_kind, UserTypeArray): + expression = self.expr(expr) + "%usertype_array_element" self.emit( "{name}{subscript_str} = {expr}" .format( name=assignee_fortran_name, subscript_str=subscript_str, - expr=self.expr(expr))) + expr=expression)) + if (isinstance(expr, Variable) + and isinstance(expr_kind, UserTypeArray)): + self.emitter.__exit__(None, None, None) else: - ftype = self.get_fortran_type_for_user_type(sym_kind.identifier) - AssignmentEmitter(self)( - ftype, assignee_fortran_name, {}, expr, - is_rhs_target=True) + self.emit( + "{name}{subscript_str} = {expr}" + .format( + name=assignee_fortran_name, + subscript_str=subscript_str, + expr=self.expr(expr))) self.emit("") @@ -2078,7 +2189,8 @@ def emit_assign_expr(self, assignee_sym, assignee_subscript, expr): sym_kind = self.sym_kind_table.get( self.current_function, assignee_sym) - if assignee_subscript and not isinstance(sym_kind, Array): + if (assignee_subscript and not isinstance(sym_kind, Array) + and not isinstance(sym_kind, UserTypeArray)): raise TypeError("only arrays support subscripted assignment") return @@ -2364,6 +2476,39 @@ def codegen_builtin_norm_2(results, function, args, arg_kinds, code_generator.emit("") +# FIXME: this is currently a placeholder +def codegen_builtin_norm_wrms(results, function, args, arg_kinds, + code_generator): + result, = results + + from dagrt.data import Scalar, UserType, Array + x_kind = arg_kinds[0] + if isinstance(x_kind, Scalar): + if x_kind.is_real_valued: + ftype = BuiltinType("real*8") + else: + ftype = BuiltinType("complex*16") + elif isinstance(x_kind, UserType): + ftype = code_generator.user_type_map[x_kind.identifier] + + elif isinstance(x_kind, Array): + code_generator.emit("{result} = norm2({arg})".format( + result=result, arg=args[0])) + return + + else: + raise TypeError("unsupported kind for norm_2 argument: %s" % x_kind) + + code_generator.emit(f"{result} = 0") + code_generator.emit("") + + Norm2Computer(code_generator, result)(ftype, args[0], {}) + + code_generator.emit("") + code_generator.emit("{result} = sqrt({result})".format(result=result)) + code_generator.emit("") + + class LenComputer(TypeVisitorWithResult): # FIXME: This could be made *way* more efficient by handling # arrays of built-in types directly. @@ -2449,6 +2594,34 @@ def codegen_builtin_isnan(results, function, args, arg_kinds, """) +def codegen_builtin_array_utype(results, function, args, arg_kinds, + code_generator): + result, = results + + from dagrt.data import UserType + x_kind = arg_kinds[1] + if isinstance(x_kind, UserType): + alloc_check_name = code_generator.get_alloc_check_name(x_kind.identifier) + else: + raise TypeError("unsupported kind for array_utype argument: %s" % x_kind) + + from dagrt.data import Scalar + code_generator.emit_variable_decl("i", sym_kind=Scalar(is_real_valued=True), + is_argument=False) + code_generator.emit(f"{result}%array_size = {args[0]}") + code_generator.emit(f"allocate({result}(0:{args[0]}-1))") + code_generator.emit("") + code_generator.emit(f"do i = 0, {args[0]}-1") + code_generator.emitter.indent() + code_generator.emit( + f"call {alloc_check_name}({result}(int(i))%usertype_array_element, " + + f"{result}(int(i))%dagrt_refcnt_uae") + code_generator.emitter.dedent() + code_generator.emit("end do") + + code_generator.emit("") + + UTIL_MACROS = """ <%def name="write_matrix(mat_array, rows_var)" > <% @@ -2504,11 +2677,40 @@ def kind_to_fortran(kind): if kind.is_real_valued: return "real (kind=%s)" % real_scalar_kind else: - return "compelx (kind=%s)" % complex_scalar_kind + return "complex (kind=%s)" % complex_scalar_kind %> """ +# FIXME: this is currently a placeholder +builtin_user_matmul = CallCode(UTIL_MACROS + """ + <% + a_rows = declare_new("integer", "a_rows") + b_rows = declare_new("integer", "b_rows") + res_size = declare_new("integer", "res_size") + %> + + ${check_matrix(a, a_cols, a_rows, "matmul")} + ${check_matrix(b, b_cols, b_rows, "matmul")} + + ${a_rows} = size(${a}) / int(${a_cols}) + ${b_rows} = size(${b}) / int(${b_cols}) + + ${res_size} = ${a_rows} * int(${b_cols}) + + if (allocated(${result})) then + deallocate(${result}) + endif + + allocate(${result}(0:${res_size}-1)) + + ${result} = reshape( & + matmul( & + reshape(${a}, (/${a_rows}, int(${a_cols})/)), & + reshape(${b}, (/${b_rows}, int(${b_cols})/))), & + (/${res_size}/)) + """) + builtin_matmul = CallCode(UTIL_MACROS + """ <% a_rows = declare_new("integer", "a_rows") diff --git a/dagrt/data.py b/dagrt/data.py index 342f384..fccd0b2 100644 --- a/dagrt/data.py +++ b/dagrt/data.py @@ -46,6 +46,7 @@ .. autoclass:: Scalar .. autoclass:: Array .. autoclass:: UserType +.. autoclass:: UserTypeArray Symbol kind inference ^^^^^^^^^^^^^^^^^^^^^ @@ -133,6 +134,22 @@ def __getinitargs__(self): return (self.is_real_valued,) +class UserTypeArray(SymbolKind): + """A variable-sized one-dimensional array + of user types. + + .. attribute:: identifier + + A unique identifier for this type. + """ + + def __init__(self, identifier): + super().__init__(identifier=identifier) + + def __getinitargs__(self): + return (self.identifier,) + + class UserType(SymbolKind): """Represents user state belonging to a normed vector space. @@ -424,13 +441,16 @@ def map_max(self, expr): def map_subscript(self, expr): agg_kind = self.rec(expr.aggregate) - if self.check and not isinstance(agg_kind, Array): - raise ValueError( - "only arrays can be subscripted, not '%s' " - "which is a '%s'" - % (expr.aggregate, type(agg_kind).__name__)) - - return Scalar(is_real_valued=agg_kind.is_real_valued) + if isinstance(agg_kind, Array): + return Scalar(is_real_valued=agg_kind.is_real_valued) + elif isinstance(agg_kind, UserTypeArray): + return UserType(agg_kind.identifier) + else: + if self.check: + raise ValueError( + "only arrays or UserTypeArrays can be " + "subscripted, not '%s' which is a '%s'" + % (expr.aggregate, type(agg_kind).__name__)) # }}} diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 278ac1b..d56292d 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -26,7 +26,7 @@ from pytools import RecordWithoutPickling from dagrt.data import ( - UserType, Integer, Boolean, Scalar, Array, UnableToInferKind) + UserType, Integer, Boolean, Scalar, Array, UserTypeArray, UnableToInferKind) NoneType = type(None) @@ -306,13 +306,18 @@ class NormWRMS(_NormBase): default_dict = {} def get_result_kinds(self, arg_kinds, check): - x_kind, y_kind, atol_kind, rtol_kind = self.resolve_args(arg_kinds) + x_kind, y_kind, yn_kind, atol_kind, rtol_kind = self.resolve_args(arg_kinds) if check and not isinstance(x_kind, (NoneType, Array, UserType)): - raise TypeError("argument 'x' of 'norm' is not a user type") - + raise TypeError("argument 'x' of 'wrms_norm' is not a user type") if check and not isinstance(y_kind, (NoneType, Array, UserType)): - raise TypeError("argument 'y' of 'norm' is not a user type") + raise TypeError("argument 'y' of 'wrms_norm' is not a user type") + if check and not isinstance(yn_kind, (NoneType, Array, UserType)): + raise TypeError("argument 'ynew' of 'wrms_norm' is not a user type") + if check and not isinstance(atol_kind, (Scalar, Array)): + raise TypeError("argument 'atol' of 'wrms_norm' is not a scalar/array") + if check and not isinstance(rtol_kind, (Scalar, Array)): + raise TypeError("argument 'rtol' of 'wrms_norm' is not a scalar/array") return (Scalar(is_real_valued=True),) @@ -405,8 +410,8 @@ def get_result_kinds(self, arg_kinds, check): class ArrayUType_(Function): # noqa - """``array_utype(n, x)`` returns an empty array with *n* entries in it. - *n* must be an integer. + """``array_utype(n, x)`` returns an empty array with *n* entries in it, + in which each entry is a user type. *n* must be an integer. """ result_names = ("result",) @@ -419,10 +424,12 @@ def get_result_kinds(self, arg_kinds, check): if check and not isinstance(n_kind, Scalar): raise TypeError("argument 'n' of 'array_utype' is not a scalar") - if check and not isinstance(x_kind, (NoneType, Array, UserType)): + if check and not isinstance(x_kind, UserType): raise TypeError("argument 'x' of 'array_utype' is not a user type") - return (Array(is_real_valued=True),) + # FIXME: need to robustly get usertype argument's identifier...? + #return (UserTypeArray(identifier=x_kind.identifier),) + return (UserTypeArray(identifier="y"),) class MatMul(Function): @@ -477,19 +484,19 @@ def get_result_kinds(self, arg_kinds, check): "matmul needs to know both arguments to infer result kind") if check and not isinstance(a_kind, Array): - raise TypeError("argument 'a' of 'matmul' is not an array") - if check and not isinstance(b_kind, Array): - raise TypeError("argument 'a' of 'matmul' is not an array") + raise TypeError("argument 'a' of 'user_matmul' is not an array") + if check and not isinstance(b_kind, UserTypeArray): + raise TypeError("argument 'b' of 'user_matmul' not array of UserTypes") if check and not isinstance(a_cols_kind, Scalar): - raise TypeError("argument 'a_cols' of 'matmul' is not a scalar") + raise TypeError("argument 'a_cols' of 'user_matmul' is not a scalar") if check and not isinstance(b_cols_kind, Scalar): - raise TypeError("argument 'b_cols' of 'matmul' is not a scalar") + raise TypeError("argument 'b_cols' of 'user_matmul' is not a scalar") if check and not isinstance(c_cols_kind, Scalar): - raise TypeError("argument 'c_cols' of 'matmul' is not a scalar") + raise TypeError("argument 'c_cols' of 'user_matmul' is not a scalar") - is_real_valued = a_kind.is_real_valued and b_kind.is_real_valued - - return (Array(is_real_valued),) + # FIXME: need to robustly obtain usertypearray argument's identifier... + return (UserTypeArray(identifier=b_kind.identifier,),) + #return (UserTypeArray(identifier="y",),) class Transpose(Function): @@ -644,14 +651,20 @@ def _make_bfr(): bfr = bfr.register_codegen(Norm2.identifier, "fortran", f.codegen_builtin_norm_2) + bfr = bfr.register_codegen(NormWRMS.identifier, "fortran", + f.codegen_builtin_norm_wrms) bfr = bfr.register_codegen(Len.identifier, "fortran", f.codegen_builtin_len) bfr = bfr.register_codegen(IsNaN.identifier, "fortran", f.codegen_builtin_isnan) bfr = bfr.register_codegen(Array_.identifier, "fortran", f.builtin_array) + bfr = bfr.register_codegen(ArrayUType_.identifier, "fortran", + f.codegen_builtin_array_utype) bfr = bfr.register_codegen(MatMul.identifier, "fortran", f.builtin_matmul) + bfr = bfr.register_codegen(UserMatMul.identifier, "fortran", + f.builtin_user_matmul) bfr = bfr.register_codegen(Transpose.identifier, "fortran", f.builtin_transpose) bfr = bfr.register_codegen(LinearSolve.identifier, "fortran", From 7fef308a53e4d172226aaca73b0a388a493b4191 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Wed, 26 May 2021 11:47:57 -0500 Subject: [PATCH 05/21] Fix bad quotes --- dagrt/codegen/fortran.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index c2dffa5..3cd8296 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -1287,10 +1287,10 @@ def begin_emit(self, dag): # FIXME: need to steal UserType identifier somehow. self.emit_variable_decl( - self.name_manager.name_global('usertype_array_element'), - sym_kind=UserType('y'), is_argument=False, + self.name_manager.name_global("usertype_array_element"), + sym_kind=UserType("y"), is_argument=False, refcount_name=self.name_manager.name_refcount( - 'uae', qualified_with_state=False)) + "uae", qualified_with_state=False)) # Store the length of the UserTypeArray in the type itself. from dagrt.data import Scalar self.emit_variable_decl( @@ -1479,7 +1479,7 @@ def get_fortran_type_for_user_type_array(self, type_identifier, ftype = StructureType("dagrt_usertype_array", ( ("usertype_array_element", self.get_fortran_type_for_user_type(type_identifier)), - (self.name_manager.name_refcount('uae', qualified_with_state=False), + (self.name_manager.name_refcount("uae", qualified_with_state=False), PointerType(BuiltinType("integer"))))) return ftype @@ -1657,7 +1657,7 @@ def emit_assign_expr_inner(self, # since usertype arrays are actually intermediate structures. if isinstance(sym_kind, UserTypeArray): if assignee_subscript: - subscript_str += '%usertype_array_element' + subscript_str += "%usertype_array_element" if isinstance(expr, (Call, CallWithKwargs)): # These are supposed to have been transformed to AssignFunctionCall. From fea9e9f85dd3a2f12fb8d32240e8a2524995290b Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Wed, 26 May 2021 13:07:11 -0500 Subject: [PATCH 06/21] To get existing tests to pass --- dagrt/codegen/fortran.py | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index 3cd8296..423c4a5 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -1280,22 +1280,30 @@ def begin_emit(self, dag): # {{{ for usertype arrays, should they be required - with FortranTypeEmitter( - self.emitter, - "dagrt_usertype_array", - self) as emit: + # Check if this is needed. + utype_arrays_present = False + for _identifier, sym_kind in sorted( + self.sym_kind_table.global_table.items()): + if isinstance(sym_kind, UserTypeArray): + utype_arrays_present = True + + if utype_arrays_present: + with FortranTypeEmitter( + self.emitter, + "dagrt_usertype_array", + self) as emit: - # FIXME: need to steal UserType identifier somehow. - self.emit_variable_decl( - self.name_manager.name_global("usertype_array_element"), - sym_kind=UserType("y"), is_argument=False, - refcount_name=self.name_manager.name_refcount( - "uae", qualified_with_state=False)) - # Store the length of the UserTypeArray in the type itself. - from dagrt.data import Scalar - self.emit_variable_decl( - "array_size", sym_kind=Scalar(is_real_valued=True), - is_argument=False) + # FIXME: need to steal UserType identifier somehow. + self.emit_variable_decl( + self.name_manager.name_global("usertype_array_element"), + sym_kind=UserType("y"), is_argument=False, + refcount_name=self.name_manager.name_refcount( + "uae", qualified_with_state=False)) + # Store the length of the UserTypeArray in the type itself. + from dagrt.data import Scalar + self.emit_variable_decl( + "array_size", sym_kind=Scalar(is_real_valued=True), + is_argument=False) # }}} From 50d4350d5c859cda0a381b9143a85811b837544a Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Thu, 27 May 2021 10:45:40 -0500 Subject: [PATCH 07/21] Introduce better naming for UserTypeArrays, allowing for derived types for multiple UserTypes --- dagrt/codegen/fortran.py | 21 ++++++++------------- dagrt/data.py | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index 423c4a5..cfe238c 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -1280,23 +1280,18 @@ def begin_emit(self, dag): # {{{ for usertype arrays, should they be required - # Check if this is needed. - utype_arrays_present = False - for _identifier, sym_kind in sorted( - self.sym_kind_table.global_table.items()): - if isinstance(sym_kind, UserTypeArray): - utype_arrays_present = True - - if utype_arrays_present: + # Emit a derived type for each UserTypeArray that we have. + from dagrt.data import collect_user_type_arrays + usertype_arrays = collect_user_type_arrays(self.sym_kind_table) + for sym_id in usertype_arrays: with FortranTypeEmitter( self.emitter, - "dagrt_usertype_array", + "dagrt_{}_array".format(sym_id), self) as emit: - # FIXME: need to steal UserType identifier somehow. self.emit_variable_decl( self.name_manager.name_global("usertype_array_element"), - sym_kind=UserType("y"), is_argument=False, + sym_kind=UserType(sym_id), is_argument=False, refcount_name=self.name_manager.name_refcount( "uae", qualified_with_state=False)) # Store the length of the UserTypeArray in the type itself. @@ -1484,7 +1479,7 @@ def get_fortran_type_for_user_type_array(self, type_identifier, is_argument=False): # The type is a structure type, which has two members: # a UserType and the (integer) refcount. - ftype = StructureType("dagrt_usertype_array", ( + ftype = StructureType("dagrt_{}_array".format(type_identifier), ( ("usertype_array_element", self.get_fortran_type_for_user_type(type_identifier)), (self.name_manager.name_refcount("uae", qualified_with_state=False), @@ -1541,7 +1536,7 @@ def emit_variable_decl(self, fortran_name, sym_kind, is_argument=is_argument) # This is a 1D array of UserTypes. - type_name = "type(dagrt_usertype_array)" + type_name = "type(dagrt_{}_array)".format(sym_kind.identifier) type_specifiers += ("dimension(:)",) else: diff --git a/dagrt/data.py b/dagrt/data.py index fccd0b2..fca4367 100644 --- a/dagrt/data.py +++ b/dagrt/data.py @@ -60,6 +60,7 @@ .. autofunction:: infer_kinds .. autofunction:: collect_user_types +.. autofunction:: collect_user_type_arrays """ @@ -666,4 +667,28 @@ def collect_user_types(skt): # }}} +# {{{ collect user types + + +def collect_user_type_arrays(skt): + """Collect all of the of :class:`UserTypeArray` identifiers in a table. + + :arg skt: a :class:`SymbolKindTable` + :returns: a set of strings + """ + result = set() + + for kind in skt.global_table.values(): + if isinstance(kind, UserTypeArray): + result.add(kind.identifier) + + for tbl in skt.per_phase_table.values(): + for kind in tbl.values(): + if isinstance(kind, UserTypeArray): + result.add(kind.identifier) + + return result + +# }}} + # vim: foldmethod=marker From 3788386f674d0af6d992c8e87f261a9786a9372f Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Thu, 27 May 2021 11:00:09 -0500 Subject: [PATCH 08/21] Continue fixing garbage naming conventions --- dagrt/codegen/fortran.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index cfe238c..87f56b7 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -317,7 +317,8 @@ def map_variable(self, expr): # We also need to catch UserTypeArrays here, and operate # on their elements. elif isinstance(self.find_sym_kind(expr), UserTypeArray): - expr = self.transform_utype_array(expr) + identifier = self.find_sym_kind(expr).identifier + expr = self.transform_utype_array(expr, identifier) return self.transform(expr) else: return expr @@ -343,8 +344,8 @@ def __init__(self, code_generator, subscript): def transform(self, expr): return expr[self.subscript] - def transform_utype_array(self, expr): - return expr.attr("usertype_array_element") + def transform_utype_array(self, expr, identifier): + return expr.attr(identifier) # }}} @@ -1290,10 +1291,10 @@ def begin_emit(self, dag): self) as emit: self.emit_variable_decl( - self.name_manager.name_global("usertype_array_element"), + self.name_manager.name_global(sym_id), sym_kind=UserType(sym_id), is_argument=False, refcount_name=self.name_manager.name_refcount( - "uae", qualified_with_state=False)) + sym_id, qualified_with_state=False)) # Store the length of the UserTypeArray in the type itself. from dagrt.data import Scalar self.emit_variable_decl( @@ -1480,9 +1481,10 @@ def get_fortran_type_for_user_type_array(self, type_identifier, # The type is a structure type, which has two members: # a UserType and the (integer) refcount. ftype = StructureType("dagrt_{}_array".format(type_identifier), ( - ("usertype_array_element", + (type_identifier, self.get_fortran_type_for_user_type(type_identifier)), - (self.name_manager.name_refcount("uae", qualified_with_state=False), + (self.name_manager.name_refcount(type_identifier, + qualified_with_state=False), PointerType(BuiltinType("integer"))))) return ftype @@ -1623,12 +1625,12 @@ def emit_user_type_move(self, assignee_sym, assignee_fortran_name, if isinstance(expr, Variable): expr_kind = self.sym_kind_table.get(self.current_function, expr.name) if isinstance(expr_kind, UserTypeArray): - expression = self.expr(expr) + "%usertype_array_element" + expression = self.expr(expr) + "%" + expr_kind.identifier if isinstance(expr, (Subscript, Lookup)): expr_kind = self.sym_kind_table.get(self.current_function, expr.aggregate.name) if isinstance(expr_kind, UserTypeArray): - expression = self.expr(expr) + "%usertype_array_element" + expression = self.expr(expr) + "%" + expr_kind.identifier self.emit_traceable( "{name} => {expr}" @@ -1660,7 +1662,7 @@ def emit_assign_expr_inner(self, # since usertype arrays are actually intermediate structures. if isinstance(sym_kind, UserTypeArray): if assignee_subscript: - subscript_str += "%usertype_array_element" + subscript_str += "%" + sym_kind.identifier if isinstance(expr, (Call, CallWithKwargs)): # These are supposed to have been transformed to AssignFunctionCall. @@ -1695,8 +1697,8 @@ def emit_assign_expr_inner(self, # the RHS... ident = self.name_manager["i"] expression = self.expr(expr) + \ - "({})%usertype_array_element".format(ident) - subscript_str += "({})%usertype_array_element".format(ident) + "({})%".format(ident) + expr_kind.identifier + subscript_str += "({})%".format(ident) + expr_kind.identifier em = FortranDoEmitter( self.emitter, ident, "1, {}".format( @@ -1709,7 +1711,7 @@ def emit_assign_expr_inner(self, expr_kind = self.sym_kind_table.get(self.current_function, expr.aggregate.name) if isinstance(expr_kind, UserTypeArray): - expression = self.expr(expr) + "%usertype_array_element" + expression = self.expr(expr) + "%" + expr_kind.identifier self.emit( "{name}{subscript_str} = {expr}" .format( @@ -2608,6 +2610,8 @@ def codegen_builtin_array_utype(results, function, args, arg_kinds, else: raise TypeError("unsupported kind for array_utype argument: %s" % x_kind) + refcount_name = code_generator.name_manager.name_refcount( + x_kind.identifier, qualified_with_state=False) from dagrt.data import Scalar code_generator.emit_variable_decl("i", sym_kind=Scalar(is_real_valued=True), is_argument=False) @@ -2617,8 +2621,8 @@ def codegen_builtin_array_utype(results, function, args, arg_kinds, code_generator.emit(f"do i = 0, {args[0]}-1") code_generator.emitter.indent() code_generator.emit( - f"call {alloc_check_name}({result}(int(i))%usertype_array_element, " - + f"{result}(int(i))%dagrt_refcnt_uae") + f"call {alloc_check_name}({result}(int(i))%{x_kind.identifier}, " + + f"{result}(int(i))%{refcount_name}") code_generator.emitter.dedent() code_generator.emit("end do") From ca8daee9fc81e8a6fb8924b18613cf7d58a68362 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Thu, 27 May 2021 11:08:44 -0500 Subject: [PATCH 09/21] Fix whoopsie identified by pylint --- dagrt/codegen/fortran.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index 87f56b7..17dc932 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -308,7 +308,7 @@ def find_sym_kind(self, expr): def transform(self, expr): raise NotImplementedError - def transform_utype_array(self, expr): + def transform_utype_array(self, expr, identifier): raise NotImplementedError def map_variable(self, expr): @@ -2196,7 +2196,8 @@ def emit_assign_expr(self, assignee_sym, assignee_subscript, expr): if (assignee_subscript and not isinstance(sym_kind, Array) and not isinstance(sym_kind, UserTypeArray)): - raise TypeError("only arrays support subscripted assignment") + raise TypeError("only arrays and UserTypeArrays support" + " subscripted assignment") return if not isinstance(sym_kind, UserType): From 59758c38ee360bf02fda14c155beef38c71f24a3 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Thu, 27 May 2021 11:53:48 -0500 Subject: [PATCH 10/21] Fix issue with UserTypeArray identifier inheritance --- dagrt/data.py | 7 ++++++- dagrt/function_registry.py | 6 +----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/dagrt/data.py b/dagrt/data.py index fca4367..ef8267b 100644 --- a/dagrt/data.py +++ b/dagrt/data.py @@ -389,7 +389,12 @@ def map_generic_call(self, function_id, arg_dict, single_return_only=True): except UnableToInferKind: arg_kinds[key] = None - z = func.get_result_kinds(arg_kinds, self.check) + try: + z = func.get_result_kinds(arg_kinds, self.check) + except Exception: + raise UnableToInferKind( + "function '%s' needs more info about arguments" + % function_id) if single_return_only: if len(z) != 1: diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index d56292d..096b354 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -427,9 +427,7 @@ def get_result_kinds(self, arg_kinds, check): if check and not isinstance(x_kind, UserType): raise TypeError("argument 'x' of 'array_utype' is not a user type") - # FIXME: need to robustly get usertype argument's identifier...? - #return (UserTypeArray(identifier=x_kind.identifier),) - return (UserTypeArray(identifier="y"),) + return (UserTypeArray(identifier=x_kind.identifier),) class MatMul(Function): @@ -494,9 +492,7 @@ def get_result_kinds(self, arg_kinds, check): if check and not isinstance(c_cols_kind, Scalar): raise TypeError("argument 'c_cols' of 'user_matmul' is not a scalar") - # FIXME: need to robustly obtain usertypearray argument's identifier... return (UserTypeArray(identifier=b_kind.identifier,),) - #return (UserTypeArray(identifier="y",),) class Transpose(Function): From d7ffb53d7b5a57a7aa8105f2037716ae7403b299 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Fri, 28 May 2021 10:24:14 -0500 Subject: [PATCH 11/21] Restructure builtin_array_utype to perform element allocation outside of builtin (avoids multi-UserType issue) --- dagrt/codegen/fortran.py | 63 ++++++++++++++++++++++---------------- dagrt/function_registry.py | 2 +- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index cf512e2..75a24bc 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -2343,6 +2343,32 @@ def emit_inst_AssignFunctionCall(self, inst): + assignee_fortran_names ))) + # If we just built a UserTypeArray, we need to loop and call the + # appropriate allocation check on the elements. + if "array_utype" in fortran_func_name: + alloc_check_name = self.get_alloc_check_name(sym_kind.identifier) + ident = self.name_manager["i"] + em = FortranDoEmitter( + self.emitter, ident, + "1, {}".format( + assignee_fortran_names[0] + "%array_size"), + self) + em.__enter__() + uarray_entry = assignee_fortran_names[0] + "(int({}))".format(ident) + uarray_entry += "%{}".format(sym_kind.identifier) + refcnt_name = assignee_fortran_names[0] + "(int({}))".format(ident) + refcnt_name += "%{}".format(self.name_manager.name_refcount( + sym_kind.identifier, qualified_with_state=False)) + self.emit( + "call {alloc_check_name}({args})" + .format( + alloc_check_name=alloc_check_name, + args=", ".join( + self.extra_arguments + + (uarray_entry, refcnt_name)) + )) + self.emitter.__exit__(None, None, None) + self.emit_deinit_for_last_usage_of_vars(inst) # }}} @@ -2604,34 +2630,19 @@ def codegen_builtin_isnan(results, function, args, arg_kinds, """) -def codegen_builtin_array_utype(results, function, args, arg_kinds, - code_generator): - result, = results +builtin_array_utype = CallCode(""" + if (int(${n}).ne.${n}) then + write(dagrt_stderr,*) 'argument to array_utype() is not an integer' + stop + endif - from dagrt.data import UserType - x_kind = arg_kinds[1] - if isinstance(x_kind, UserType): - alloc_check_name = code_generator.get_alloc_check_name(x_kind.identifier) - else: - raise TypeError("unsupported kind for array_utype argument: %s" % x_kind) - - refcount_name = code_generator.name_manager.name_refcount( - x_kind.identifier, qualified_with_state=False) - from dagrt.data import Scalar - code_generator.emit_variable_decl("i", sym_kind=Scalar(is_real_valued=True), - is_argument=False) - code_generator.emit(f"{result}%array_size = {args[0]}") - code_generator.emit(f"allocate({result}(0:{args[0]}-1))") - code_generator.emit("") - code_generator.emit(f"do i = 0, {args[0]}-1") - code_generator.emitter.indent() - code_generator.emit( - f"call {alloc_check_name}({result}(int(i))%{x_kind.identifier}, " - + f"{result}(int(i))%{refcount_name}") - code_generator.emitter.dedent() - code_generator.emit("end do") + if (allocated(${result})) then + deallocate(${result}) + endif - code_generator.emit("") + ${result}%array_size = ${n} + allocate(${result}(0:int(${n})-1)) + """) UTIL_MACROS = """ diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 096b354..0512814 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -656,7 +656,7 @@ def _make_bfr(): bfr = bfr.register_codegen(Array_.identifier, "fortran", f.builtin_array) bfr = bfr.register_codegen(ArrayUType_.identifier, "fortran", - f.codegen_builtin_array_utype) + f.builtin_array_utype) bfr = bfr.register_codegen(MatMul.identifier, "fortran", f.builtin_matmul) bfr = bfr.register_codegen(UserMatMul.identifier, "fortran", From 38abcafc1df86e15cefdfa9fe2545cbf0c0ae53e Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Fri, 28 May 2021 16:36:24 -0500 Subject: [PATCH 12/21] Fixes some bugs/patches some holes identified by a new dagrt-side test --- dagrt/data.py | 11 ++++++++++- test/test_codegen_fortran.py | 37 ++++++++++++++++++++++++++++++++++++ test/test_utype_arrays.f90 | 31 ++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 test/test_utype_arrays.f90 diff --git a/dagrt/data.py b/dagrt/data.py index 9a289ca..6d817bb 100644 --- a/dagrt/data.py +++ b/dagrt/data.py @@ -470,7 +470,7 @@ class SymbolKindFinder: def __init__(self, function_registry): self.function_registry = function_registry - def __call__(self, names, phases, forced_kinds=None): + def __call__(self, names, phases, forced_kinds=None, user_type_map=None): """Infer the kinds of all the symbols in a program. :arg names: a list of phase names @@ -493,6 +493,15 @@ def __call__(self, names, phases, forced_kinds=None): for phase_name, ident, kind in forced_kinds: result.set(phase_name, ident, kind=kind) + # If a UserType map is given, set the global symbol + # kind table accordingly. + # FIXME: are UserType identifiers guaranteed to + # match component_ids? + if user_type_map is not None: + for name in user_type_map: + result.set(names[0], "{}".format(name), + UserType(identifier=name)) + def make_kim(phase_name, check): return KindInferenceMapper( result.global_table, diff --git a/test/test_codegen_fortran.py b/test/test_codegen_fortran.py index 420efd3..84a0ae7 100755 --- a/test/test_codegen_fortran.py +++ b/test/test_codegen_fortran.py @@ -150,6 +150,43 @@ def test_self_dep_in_loop(): fortran_libraries=["lapack", "blas"]) +def test_usertype_arrays(): + with CodeBuilder(name="primary") as cb: + cb("y_array", "array_utype(5, ytype)") + cb("y_array[i]", "ytype", + loops=(("i", 0, 5),)) + cb("y_array[i]", "i*f(0, y_array[i])", + loops=(("i", 0, 5),)) + + code = create_DAGCode_with_steady_phase(cb.statements) + + rhs_function = "f" + + from dagrt.function_registry import ( + base_function_registry, register_ode_rhs) + freg = register_ode_rhs(base_function_registry, "ytype", + identifier=rhs_function, + input_names=("y",)) + freg = freg.register_codegen(rhs_function, "fortran", + f.CallCode(""" + ${result} = -2*${y} + """)) + + codegen = f.CodeGenerator( + "utype_arrays", + function_registry=freg, + user_type_map={"ytype": f.ArrayType((100,), f.BuiltinType("real*8"))}, + timing_function="second") + + code_str = codegen(code) + + run_fortran([ + ("utype_arrays.f90", code_str), + ("test_utype_arrays.f90", read_file("test_utype_arrays.f90")), + ], + fortran_libraries=["lapack", "blas"]) + + if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) diff --git a/test/test_utype_arrays.f90 b/test/test_utype_arrays.f90 new file mode 100644 index 0000000..5eb10e5 --- /dev/null +++ b/test/test_utype_arrays.f90 @@ -0,0 +1,31 @@ +program test_utype_arrays + + use utype_arrays, only: dagrt_state_type, & + timestep_initialize => initialize, & + timestep_run => run, & + timestep_shutdown => shutdown + + implicit none + + type(dagrt_state_type), target :: dagrt_state + type(dagrt_state_type), pointer :: dagrt_state_ptr + + real*8, dimension(100) :: y0 + + integer i + + ! start code ---------------------------------------------------------------- + + dagrt_state_ptr => dagrt_state + + + do i = 1, 100 + y0(i) = i + end do + + call timestep_initialize(dagrt_state=dagrt_state_ptr, state_ytype=y0) + call timestep_run(dagrt_state=dagrt_state_ptr) + call timestep_shutdown(dagrt_state=dagrt_state_ptr) + +end program + From a58c7c24b3fe1e3b881406c42eb86f16ae191cea Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Fri, 28 May 2021 16:44:10 -0500 Subject: [PATCH 13/21] Scrubs PR of any builtins not directly pertaining to addition of UserTypeArrays --- dagrt/builtins_python.py | 41 ------------ dagrt/codegen/fortran.py | 131 ++++++++++++++++--------------------- dagrt/function_registry.py | 65 ------------------ 3 files changed, 58 insertions(+), 179 deletions(-) diff --git a/dagrt/builtins_python.py b/dagrt/builtins_python.py index 2b9f582..97255e3 100644 --- a/dagrt/builtins_python.py +++ b/dagrt/builtins_python.py @@ -47,25 +47,6 @@ def builtin_norm_2(x): return np.linalg.norm(x, 2) -def builtin_norm_wrms(x, y, ynew, atol, rtol): - import numpy as np - if np.isscalar(x): - return abs(x) - # Use y to calculate weights. - w = np.zeros(len(y)) - for i in range(len(y)): - if np.isscalar(rtol): - w[i] = 1.0/(rtol*abs(ynew[i]) + atol) - else: - w[i] = 1.0/(rtol[i]*abs(ynew[i]) + atol) - # now calculate the wrms norm - add = 0 - for i in range(len(x)): - add += (w[i]*x[i])**2 - - return np.sqrt(add/len(y)) - - def builtin_norm_inf(x): import numpy as np if np.isscalar(x): @@ -114,26 +95,6 @@ def builtin_matmul(a, b, a_cols, b_cols): return res_mat.reshape(-1, order="F") -def builtin_user_matmul(a, b, a_cols, b_cols, c_cols): - import numpy as np - if a_cols != np.floor(a_cols): - raise ValueError("matmul() argument a_cols is not an integer") - if b_cols != np.floor(b_cols): - raise ValueError("matmul() argument b_cols is not an integer") - if c_cols != np.floor(c_cols): - raise ValueError("matmul() argument b_cols is not an integer") - a_cols = int(a_cols) - b_cols = int(b_cols) - c_cols = int(c_cols) - - a_mat = a.reshape(-1, a_cols, order="F") - b_mat = b.reshape(-1, b_cols, order="F") - - res_mat = a_mat.dot(b_mat) - - return res_mat.reshape(-1, c_cols, order="F") - - def builtin_transpose(a, a_cols): import numpy as np if a_cols != np.floor(a_cols): @@ -188,13 +149,11 @@ def builtin_print(arg): "isnan": builtin_isnan, "norm_1": builtin_norm_1, "norm_2": builtin_norm_2, - "norm_wrms": builtin_norm_wrms, "norm_inf": builtin_norm_inf, "dot_product": builtin_dot_product, "array": builtin_array, "array_utype": builtin_array_utype, "matmul": builtin_matmul, - "user_matmul": builtin_user_matmul, "transpose": builtin_transpose, "linear_solve": builtin_linear_solve, "svd": builtin_svd, diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index 75a24bc..80cbfba 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -1103,7 +1103,8 @@ def process_ast(ast, print_ast=False): forced_kinds=[ (fd.name, loop_var, Integer()) for fd in fdescrs - for loop_var in LoopVariableFinder()(fd.ast)]) + for loop_var in LoopVariableFinder()(fd.ast)], + user_type_map=self.user_type_map) from dagrt.codegen.analysis import ( collect_ode_component_names_from_dag, @@ -1307,9 +1308,9 @@ def begin_emit(self, dag): refcount_name=self.name_manager.name_refcount( sym_id, qualified_with_state=False)) # Store the length of the UserTypeArray in the type itself. - from dagrt.data import Scalar + from dagrt.data import Integer self.emit_variable_decl( - "array_size", sym_kind=Scalar(is_real_valued=True), + "array_size", sym_kind=Integer(), is_argument=False) # }}} @@ -1401,7 +1402,6 @@ def begin_emit(self, dag): # If the refcount is 1, then nobody else is referring to # the memory, and we might as well repurpose/overwrite it, # so there's nothing more to do in that case. - with FortranIfEmitter( self.emitter, "refcount.ne.1", self) as emit_if: @@ -1550,7 +1550,7 @@ def emit_variable_decl(self, fortran_name, sym_kind, # This is a 1D array of UserTypes. type_name = "type(dagrt_{}_array)".format(sym_kind.identifier) - type_specifiers += ("dimension(:)",) + type_specifiers += ("allocatable, dimension(:)",) else: raise ValueError("unknown variable kind: %s" % type(sym_kind).__name__) @@ -1706,14 +1706,53 @@ def emit_assign_expr_inner(self, # We need to loop through the UserTypeArray here, # and attach the structure entrance to both the LHS and # the RHS... - ident = self.name_manager["i"] + # For temps, we also need to check if the assignee is + # allocated, and if not, allocate it. + with FortranIfEmitter( + self.emitter, ".not.allocated({})".format( + assignee_fortran_name), self): + self.emit("allocate({}(0:{}-1))".format( + assignee_fortran_name, + self.expr(expr) + "(0)%array_size")) + alloc_check_name = self.get_alloc_check_name( + sym_kind.identifier) + ident = self.name_manager.make_unique_fortran_name( + "uarray_i") + self.declaration_emitter("integer %s" % ident) + em = FortranDoEmitter( + self.emitter, ident, + "0, {}".format( + self.expr(expr) + "(0)%array_size-1"), + self) + em.__enter__() + uarray_entry = assignee_fortran_name + \ + "(int({}))".format(ident) + uarray_entry += "%{}".format(sym_kind.identifier) + refcnt_name = assignee_fortran_name + "(int({}))".format( + ident) + refcnt_name += "%{}".format( + self.name_manager.name_refcount( + sym_kind.identifier, + qualified_with_state=False)) + self.emit( + "call {alloc_check_name}({args})" + .format( + alloc_check_name=alloc_check_name, + args=", ".join( + self.extra_arguments + + (uarray_entry, refcnt_name)) + )) + self.emitter.__exit__(None, None, None) + ident = self.name_manager.make_unique_fortran_name( + "uarray_i") expression = self.expr(expr) + \ "({})%".format(ident) + expr_kind.identifier subscript_str += "({})%".format(ident) + expr_kind.identifier + self.declaration_emitter("integer %s" % ident) em = FortranDoEmitter( self.emitter, ident, - "1, {}".format( - self.expr(expr) + "%array_size"), + "0, {}".format( + self.expr(expr) + "(0)%array_size-1"), self) em.__enter__() @@ -2347,11 +2386,12 @@ def emit_inst_AssignFunctionCall(self, inst): # appropriate allocation check on the elements. if "array_utype" in fortran_func_name: alloc_check_name = self.get_alloc_check_name(sym_kind.identifier) - ident = self.name_manager["i"] + ident = self.name_manager.make_unique_fortran_name("uarray_i") + self.declaration_emitter("integer %s" % ident) em = FortranDoEmitter( self.emitter, ident, - "1, {}".format( - assignee_fortran_names[0] + "%array_size"), + "0, {}".format( + assignee_fortran_names[0] + "(0)%array_size-1"), self) em.__enter__() uarray_entry = assignee_fortran_names[0] + "(int({}))".format(ident) @@ -2512,39 +2552,6 @@ def codegen_builtin_norm_2(results, function, args, arg_kinds, code_generator.emit("") -# FIXME: this is currently a placeholder -def codegen_builtin_norm_wrms(results, function, args, arg_kinds, - code_generator): - result, = results - - from dagrt.data import Scalar, UserType, Array - x_kind = arg_kinds[0] - if isinstance(x_kind, Scalar): - if x_kind.is_real_valued: - ftype = BuiltinType("real*8") - else: - ftype = BuiltinType("complex*16") - elif isinstance(x_kind, UserType): - ftype = code_generator.user_type_map[x_kind.identifier] - - elif isinstance(x_kind, Array): - code_generator.emit("{result} = norm2({arg})".format( - result=result, arg=args[0])) - return - - else: - raise TypeError("unsupported kind for norm_2 argument: %s" % x_kind) - - code_generator.emit(f"{result} = 0") - code_generator.emit("") - - Norm2Computer(code_generator, result)(ftype, args[0], {}) - - code_generator.emit("") - code_generator.emit("{result} = sqrt({result})".format(result=result)) - code_generator.emit("") - - class LenComputer(TypeVisitorWithResult): # FIXME: This could be made *way* more efficient by handling # arrays of built-in types directly. @@ -2631,6 +2638,9 @@ def codegen_builtin_isnan(results, function, args, arg_kinds, builtin_array_utype = CallCode(""" + <% + i = declare_new("integer", "i") + %> if (int(${n}).ne.${n}) then write(dagrt_stderr,*) 'argument to array_utype() is not an integer' stop @@ -2640,8 +2650,11 @@ def codegen_builtin_isnan(results, function, args, arg_kinds, deallocate(${result}) endif - ${result}%array_size = ${n} allocate(${result}(0:int(${n})-1)) + + do ${i} = 0, int(${n})-1 + ${result}(int(${i}))%array_size = int(${n}) + end do """) @@ -2705,34 +2718,6 @@ def kind_to_fortran(kind): """ -# FIXME: this is currently a placeholder -builtin_user_matmul = CallCode(UTIL_MACROS + """ - <% - a_rows = declare_new("integer", "a_rows") - b_rows = declare_new("integer", "b_rows") - res_size = declare_new("integer", "res_size") - %> - - ${check_matrix(a, a_cols, a_rows, "matmul")} - ${check_matrix(b, b_cols, b_rows, "matmul")} - - ${a_rows} = size(${a}) / int(${a_cols}) - ${b_rows} = size(${b}) / int(${b_cols}) - - ${res_size} = ${a_rows} * int(${b_cols}) - - if (allocated(${result})) then - deallocate(${result}) - endif - - allocate(${result}(0:${res_size}-1)) - - ${result} = reshape( & - matmul( & - reshape(${a}, (/${a_rows}, int(${a_cols})/)), & - reshape(${b}, (/${b_rows}, int(${b_cols})/))), & - (/${res_size}/)) - """) builtin_matmul = CallCode(UTIL_MACROS + """ <% diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 0512814..30a5270 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -296,32 +296,6 @@ class Norm2(_NormBase): identifier = "norm_2" -class NormWRMS(_NormBase): - """``norm_wrms(x)`` returns the wrms-norm of *x*. - *x* is a user type or array, and *y* is also a user type - or array. - """ - identifier = "norm_wrms" - arg_names = ("x", "y", "ynew", "atol", "rtol") - default_dict = {} - - def get_result_kinds(self, arg_kinds, check): - x_kind, y_kind, yn_kind, atol_kind, rtol_kind = self.resolve_args(arg_kinds) - - if check and not isinstance(x_kind, (NoneType, Array, UserType)): - raise TypeError("argument 'x' of 'wrms_norm' is not a user type") - if check and not isinstance(y_kind, (NoneType, Array, UserType)): - raise TypeError("argument 'y' of 'wrms_norm' is not a user type") - if check and not isinstance(yn_kind, (NoneType, Array, UserType)): - raise TypeError("argument 'ynew' of 'wrms_norm' is not a user type") - if check and not isinstance(atol_kind, (Scalar, Array)): - raise TypeError("argument 'atol' of 'wrms_norm' is not a scalar/array") - if check and not isinstance(rtol_kind, (Scalar, Array)): - raise TypeError("argument 'rtol' of 'wrms_norm' is not a scalar/array") - - return (Scalar(is_real_valued=True),) - - class NormInf(_NormBase): """``norm_inf(x)`` returns the infinity-norm of *x*. *x* is a user type or array. @@ -462,39 +436,6 @@ def get_result_kinds(self, arg_kinds, check): return (Array(is_real_valued),) -class UserMatMul(Function): - """``matmul(a, b, a_cols, b_cols, c_cols)`` returns a 1D array containing the - matrix resulting from multiplying the arrays *a* and *b* (both interpreted - as matrices, with a number of columns *a_cols* and *b_cols* respectively). - """ - - result_names = ("result",) - identifier = "user_matmul" - arg_names = ("a", "b", "a_cols", "b_cols", "c_cols") - default_dict = {} - - def get_result_kinds(self, arg_kinds, check): - [a_kind, b_kind, a_cols_kind, - b_cols_kind, c_cols_kind] = self.resolve_args(arg_kinds) - - if a_kind is None or b_kind is None: - raise UnableToInferKind( - "matmul needs to know both arguments to infer result kind") - - if check and not isinstance(a_kind, Array): - raise TypeError("argument 'a' of 'user_matmul' is not an array") - if check and not isinstance(b_kind, UserTypeArray): - raise TypeError("argument 'b' of 'user_matmul' not array of UserTypes") - if check and not isinstance(a_cols_kind, Scalar): - raise TypeError("argument 'a_cols' of 'user_matmul' is not a scalar") - if check and not isinstance(b_cols_kind, Scalar): - raise TypeError("argument 'b_cols' of 'user_matmul' is not a scalar") - if check and not isinstance(c_cols_kind, Scalar): - raise TypeError("argument 'c_cols' of 'user_matmul' is not a scalar") - - return (UserTypeArray(identifier=b_kind.identifier,),) - - class Transpose(Function): """``transpose(a, a_cols)`` returns a 1D array containing the matrix resulting from transposing the array *a* (interpreted @@ -621,7 +562,6 @@ def _make_bfr(): for func, py_pattern in [ (Norm1(), "self._builtin_norm_1({args})"), (Norm2(), "self._builtin_norm_2({args})"), - (NormWRMS(), "self._builtin_norm_wrms({args})"), (NormInf(), "self._builtin_norm_inf({args})"), (DotProduct(), "{numpy}.vdot({args})"), (Len(), "{numpy}.size({args})"), @@ -629,7 +569,6 @@ def _make_bfr(): (Array_(), "self._builtin_array({args})"), (ArrayUType_(), "self._builtin_array_utype({args})"), (MatMul(), "self._builtin_matmul({args})"), - (UserMatMul(), "self._builtin_user_matmul({args})"), (Transpose(), "self._builtin_transpose({args})"), (LinearSolve(), "self._builtin_linear_solve({args})"), (Print(), "self._builtin_print({args})"), @@ -647,8 +586,6 @@ def _make_bfr(): bfr = bfr.register_codegen(Norm2.identifier, "fortran", f.codegen_builtin_norm_2) - bfr = bfr.register_codegen(NormWRMS.identifier, "fortran", - f.codegen_builtin_norm_wrms) bfr = bfr.register_codegen(Len.identifier, "fortran", f.codegen_builtin_len) bfr = bfr.register_codegen(IsNaN.identifier, "fortran", @@ -659,8 +596,6 @@ def _make_bfr(): f.builtin_array_utype) bfr = bfr.register_codegen(MatMul.identifier, "fortran", f.builtin_matmul) - bfr = bfr.register_codegen(UserMatMul.identifier, "fortran", - f.builtin_user_matmul) bfr = bfr.register_codegen(Transpose.identifier, "fortran", f.builtin_transpose) bfr = bfr.register_codegen(LinearSolve.identifier, "fortran", From 04ca33645ef73fb9ae90a68ad1e479df718068bc Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Fri, 28 May 2021 16:46:10 -0500 Subject: [PATCH 14/21] Failure to remove leftover doc stuff --- dagrt/function_registry.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 30a5270..6f740ac 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -59,7 +59,6 @@ .. autoclass:: Norm1 .. autoclass:: Norm2 -.. autoclass:: NormWRMS .. autoclass:: NormInf .. autoclass:: DotProduct .. autoclass:: Len @@ -67,7 +66,6 @@ .. autoclass:: Array_ .. autoclass:: ArrayUType_ .. autoclass:: MatMul -.. autoclass:: UserMatMul .. autoclass:: Transpose .. autoclass:: LinearSolve .. autoclass:: SVD From 27d96ea0fe2a387a94dc88b9817458260170c589 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Thu, 10 Jun 2021 10:00:06 -0500 Subject: [PATCH 15/21] Handling UserTypeArray assignments more robustly, deferring deinit to function end for temps --- dagrt/codegen/fortran.py | 219 +++++++++++++++++++++++++-------------- 1 file changed, 140 insertions(+), 79 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index 80cbfba..96e60bb 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -336,6 +336,24 @@ def transform(self, expr): return expr.attr(self.component) +class UserTypeArrayAppender(UserTypeReferenceTransformer): + def __init__(self, code_generator, component): + super().__init__(code_generator) + self.component = component + + def map_variable(self, expr): + if isinstance(self.find_sym_kind(expr), UserTypeArray): + return self.transform(expr) + else: + return expr + + def transform(self, expr): + return expr.attr(self.component) + + map_lookup = map_variable + map_subscript = map_variable + + class ArraySubscriptAppender(UserTypeReferenceTransformer): def __init__(self, code_generator, subscript): super().__init__(code_generator) @@ -1195,6 +1213,9 @@ def lower_function(self, function_name, ast): for identifier, sym_kind in sorted(sym_table.items()): if (identifier, self.current_function) not in self.last_used_stmt_table: self.emit_variable_deinit(identifier, sym_kind) + # For now defer UserTypeArray dealloc until function end. + if isinstance(sym_kind, UserTypeArray): + self.emit_variable_deinit(identifier, sym_kind) # }}} @@ -1575,20 +1596,52 @@ def emit_variable_deinit(self, name, sym_kind): fortran_name = self.name_manager[name] refcnt_name = self.name_manager.name_refcount(name) - from dagrt.data import UserType - if not isinstance(sym_kind, UserType): + if isinstance(sym_kind, UserType): + self.emit( + "call {var_deinit_name}({args})" + .format( + var_deinit_name=self.get_var_deinit_name( + sym_kind.identifier), + args=", ".join( + self.extra_arguments + + (fortran_name, refcnt_name)) + )) + elif isinstance(sym_kind, UserTypeArray): + # For UserTypeArrays we need to loop through and deinit as well. + with FortranIfEmitter( + self.emitter, "allocated({})".format( + fortran_name), self): + ident = self.name_manager.make_unique_fortran_name("uarray_i") + self.declaration_emitter("integer %s" % ident) + em = FortranDoEmitter( + self.emitter, ident, + "0, {}".format( + fortran_name + "(0)%array_size-1"), + self) + em.__enter__() + uarray_entry = fortran_name + \ + "(int({}))".format(ident) + uarray_entry += "%{}".format(sym_kind.identifier) + refcnt_name = fortran_name \ + + "(int({}))".format(ident) + refcnt_name += "%{}".format( + self.name_manager.name_refcount( + sym_kind.identifier, + qualified_with_state=False)) + self.emit( + "call {var_deinit_name}({args})" + .format( + var_deinit_name=self.get_var_deinit_name( + sym_kind.identifier), + args=", ".join( + self.extra_arguments + + (uarray_entry, refcnt_name)) + )) + self.emitter.__exit__(None, None, None) + self.emit("deallocate({})".format(fortran_name)) + else: return - self.emit( - "call {var_deinit_name}({args})" - .format( - var_deinit_name=self.get_var_deinit_name( - sym_kind.identifier), - args=", ".join( - self.extra_arguments - + (fortran_name, refcnt_name)) - )) - def emit_refcounted_allocation(self, sym, sym_kind): fortran_name = self.name_manager[sym] @@ -1675,6 +1728,8 @@ def emit_assign_expr_inner(self, if assignee_subscript: subscript_str += "%" + sym_kind.identifier + expr_kind = None + if isinstance(expr, (Call, CallWithKwargs)): # These are supposed to have been transformed to AssignFunctionCall. raise RuntimeError("bare Call/CallWithKwargs encountered in " @@ -1686,91 +1741,92 @@ def emit_assign_expr_inner(self, subscript_str=subscript_str, expr=str(expr)[:50])) - from dagrt.data import UserType if isinstance(sym_kind, UserType): ftype = self.get_fortran_type_for_user_type(sym_kind.identifier) AssignmentEmitter(self)( ftype, assignee_fortran_name, {}, expr, is_rhs_target=True) elif isinstance(sym_kind, UserTypeArray): - # This will require a specialized assignment emitter, - # wherein the RHS expression is transformed to insert - # the proper structure stuff for UserTypeArrays. ftype = self.get_fortran_type_for_user_type_array( sym_kind.identifier) - expression = self.expr(expr) - if isinstance(expr, Variable): - expr_kind = self.sym_kind_table.get(self.current_function, - expr.name) - if isinstance(expr_kind, UserTypeArray): - # We need to loop through the UserTypeArray here, - # and attach the structure entrance to both the LHS and - # the RHS... - # For temps, we also need to check if the assignee is - # allocated, and if not, allocate it. - with FortranIfEmitter( - self.emitter, ".not.allocated({})".format( - assignee_fortran_name), self): - self.emit("allocate({}(0:{}-1))".format( - assignee_fortran_name, - self.expr(expr) + "(0)%array_size")) - alloc_check_name = self.get_alloc_check_name( - sym_kind.identifier) + if (subscript_str or isinstance(expr, (Subscript, Lookup))): + transformer = UserTypeArrayAppender( + self, sym_kind.identifier) + expression = self.expr(transformer(expr)) + else: + expression = self.expr(expr) + if isinstance(expr, Variable): + expr_kind = self.sym_kind_table.get(self.current_function, + expr.name) + if isinstance(expr_kind, UserTypeArray): + # We need to loop through the UserTypeArray here, + # and attach the structure entrance to both the LHS and + # the RHS... + # For temps, we also need to check if the assignee is + # allocated, and if not, allocate it. + with FortranIfEmitter( + self.emitter, ".not.allocated({})".format( + assignee_fortran_name), self): + self.emit("allocate({}(0:{}-1))".format( + assignee_fortran_name, + self.expr(expr) + "(0)%array_size")) + alloc_check_name = self.get_alloc_check_name( + sym_kind.identifier) + ident = self.name_manager.make_unique_fortran_name( + "uarray_i") + self.declaration_emitter("integer %s" % ident) + em = FortranDoEmitter( + self.emitter, ident, + "0, {}".format( + self.expr(expr) + "(0)%array_size-1"), + self) + em.__enter__() + uarray_entry = assignee_fortran_name + \ + "(int({}))".format(ident) + self.emit( + "{out}%array_size = {existing}".format( + out=uarray_entry, + existing=self.expr(expr) + + "(0)%array_size")) + uarray_entry += "%{}".format(sym_kind.identifier) + refcnt_name = assignee_fortran_name \ + + "(int({}))".format(ident) + refcnt_name += "%{}".format( + self.name_manager.name_refcount( + sym_kind.identifier, + qualified_with_state=False)) + self.emit("allocate({})".format(refcnt_name)) + self.emit( + "call {alloc_check_name}({args})" + .format( + alloc_check_name=alloc_check_name, + args=", ".join( + self.extra_arguments + + (uarray_entry, refcnt_name)) + )) + self.emitter.__exit__(None, None, None) ident = self.name_manager.make_unique_fortran_name( - "uarray_i") + "uarray_i") + expression = self.expr(expr) + \ + "({})%".format(ident) + expr_kind.identifier + subscript_str += "({})%".format(ident) \ + + expr_kind.identifier self.declaration_emitter("integer %s" % ident) em = FortranDoEmitter( - self.emitter, ident, - "0, {}".format( - self.expr(expr) + "(0)%array_size-1"), - self) + self.emitter, ident, + "0, {}".format( + self.expr(expr) + "(0)%array_size-1"), + self) em.__enter__() - uarray_entry = assignee_fortran_name + \ - "(int({}))".format(ident) - uarray_entry += "%{}".format(sym_kind.identifier) - refcnt_name = assignee_fortran_name + "(int({}))".format( - ident) - refcnt_name += "%{}".format( - self.name_manager.name_refcount( - sym_kind.identifier, - qualified_with_state=False)) - self.emit( - "call {alloc_check_name}({args})" - .format( - alloc_check_name=alloc_check_name, - args=", ".join( - self.extra_arguments - + (uarray_entry, refcnt_name)) - )) - self.emitter.__exit__(None, None, None) - ident = self.name_manager.make_unique_fortran_name( - "uarray_i") - expression = self.expr(expr) + \ - "({})%".format(ident) + expr_kind.identifier - subscript_str += "({})%".format(ident) + expr_kind.identifier - self.declaration_emitter("integer %s" % ident) - em = FortranDoEmitter( - self.emitter, ident, - "0, {}".format( - self.expr(expr) + "(0)%array_size-1"), - self) - em.__enter__() - - if isinstance(expr, (Subscript, Lookup)): - # We're indexing into a UserTypeArray here. - expr_kind = self.sym_kind_table.get(self.current_function, - expr.aggregate.name) - if isinstance(expr_kind, UserTypeArray): - expression = self.expr(expr) + "%" + expr_kind.identifier self.emit( "{name}{subscript_str} = {expr}" .format( name=assignee_fortran_name, subscript_str=subscript_str, expr=expression)) - if (isinstance(expr, Variable) - and isinstance(expr_kind, UserTypeArray)): - self.emitter.__exit__(None, None, None) + if isinstance(expr, Variable): + if isinstance(expr_kind, UserTypeArray): + self.emitter.__exit__(None, None, None) else: self.emit( "{name}{subscript_str} = {expr}" @@ -2468,6 +2524,11 @@ def emit_deinit_for_last_usage_of_vars(self, inst): except KeyError: continue + # Defer deinit of these until the end of the function, + # in case they come up in loops. + if isinstance(var_kind, UserTypeArray): + continue + last_used_stmt_id = self.last_used_stmt_table[ variable, self.current_function] if inst.id == last_used_stmt_id and not is_state_variable(variable): From a5ef622c38d0d1ab572224eb95eaf021835903b7 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Thu, 10 Jun 2021 10:40:48 -0500 Subject: [PATCH 16/21] Actually use refcounts instead of casually ignoring them in UserTypeArrays --- dagrt/codegen/fortran.py | 59 ++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index 96e60bb..6ab3ada 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -1753,6 +1753,12 @@ def emit_assign_expr_inner(self, transformer = UserTypeArrayAppender( self, sym_kind.identifier) expression = self.expr(transformer(expr)) + self.emit( + "{name}{subscript_str} = {expr}" + .format( + name=assignee_fortran_name, + subscript_str=subscript_str, + expr=expression)) else: expression = self.expr(expr) if isinstance(expr, Variable): @@ -1811,6 +1817,18 @@ def emit_assign_expr_inner(self, "({})%".format(ident) + expr_kind.identifier subscript_str += "({})%".format(ident) \ + expr_kind.identifier + tgt_refcnt_name = assignee_fortran_name \ + + "(int({}))".format(ident) + tgt_refcnt_name += "%{}".format( + self.name_manager.name_refcount( + sym_kind.identifier, + qualified_with_state=False)) + refcnt_name = self.expr(expr) \ + + "(int({}))".format(ident) + refcnt_name += "%{}".format( + self.name_manager.name_refcount( + sym_kind.identifier, + qualified_with_state=False)) self.declaration_emitter("integer %s" % ident) em = FortranDoEmitter( self.emitter, ident, @@ -1818,15 +1836,34 @@ def emit_assign_expr_inner(self, self.expr(expr) + "(0)%array_size-1"), self) em.__enter__() - self.emit( - "{name}{subscript_str} = {expr}" - .format( - name=assignee_fortran_name, - subscript_str=subscript_str, - expr=expression)) - if isinstance(expr, Variable): - if isinstance(expr_kind, UserTypeArray): - self.emitter.__exit__(None, None, None) + # Handle refcounts here as well - we are essentially + # doing per-element user type moves. + self.emit( + "{name}{subscript_str} => {expr}" + .format( + name=assignee_fortran_name, + subscript_str=subscript_str, + expr=expression)) + self.emit("{tgt_refcnt} => {refcnt}".format( + tgt_refcnt=tgt_refcnt_name, + refcnt=refcnt_name)) + self.emit("{tgt_refcnt} = {tgt_refcnt} + 1".format( + tgt_refcnt=tgt_refcnt_name)) + self.emitter.__exit__(None, None, None) + else: + self.emit( + "{name}{subscript_str} = {expr}" + .format( + name=assignee_fortran_name, + subscript_str=subscript_str, + expr=expression)) + else: + self.emit( + "{name}{subscript_str} = {expr}" + .format( + name=assignee_fortran_name, + subscript_str=subscript_str, + expr=expression)) else: self.emit( "{name}{subscript_str} = {expr}" @@ -2455,6 +2492,10 @@ def emit_inst_AssignFunctionCall(self, inst): refcnt_name = assignee_fortran_names[0] + "(int({}))".format(ident) refcnt_name += "%{}".format(self.name_manager.name_refcount( sym_kind.identifier, qualified_with_state=False)) + # Ensure allocations will be performed by setting refcounts to + # non-unity. + self.emit("allocate({})".format(refcnt_name)) + self.emit("{} = 2".format(refcnt_name)) self.emit( "call {alloc_check_name}({args})" .format( From c70367f7c346eaedc72fa00d7e031839158d4539 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Thu, 10 Jun 2021 11:33:47 -0500 Subject: [PATCH 17/21] Initial foray into cutting down on duplicate/messy code, in part by reusing user_type_move --- dagrt/codegen/fortran.py | 117 ++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 64 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index 6ab3ada..4bed447 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -1679,22 +1679,9 @@ def emit_allocation_check(self, sym, sym_kind): )) def emit_user_type_move(self, assignee_sym, assignee_fortran_name, - sym_kind, expr): - self.emit_variable_deinit(assignee_sym, sym_kind) - - # We need to check if the RHS of the user type move is an - # element of a UserTypeArray - if so, we need to add the - # entrance to the derived type. - expression = self.name_manager[expr.name] - if isinstance(expr, Variable): - expr_kind = self.sym_kind_table.get(self.current_function, expr.name) - if isinstance(expr_kind, UserTypeArray): - expression = self.expr(expr) + "%" + expr_kind.identifier - if isinstance(expr, (Subscript, Lookup)): - expr_kind = self.sym_kind_table.get(self.current_function, - expr.aggregate.name) - if isinstance(expr_kind, UserTypeArray): - expression = self.expr(expr) + "%" + expr_kind.identifier + sym_kind, expression, tgt_refcnt, refcnt, deinit=True): + if deinit: + self.emit_variable_deinit(assignee_sym, sym_kind) self.emit_traceable( "{name} => {expr}" @@ -1704,15 +1691,15 @@ def emit_user_type_move(self, assignee_sym, assignee_fortran_name, self.emit_traceable( "{tgt_refcnt} => {refcnt}" .format( - tgt_refcnt=self.name_manager.name_refcount(assignee_sym), - refcnt=self.name_manager.name_refcount(expr.name))) + tgt_refcnt=tgt_refcnt, + refcnt=refcnt)) self.emit_traceable( "{tgt_refcnt} = {tgt_refcnt} + 1" .format( - tgt_refcnt=self.name_manager.name_refcount(assignee_sym))) + tgt_refcnt=tgt_refcnt)) self.emit("") - def emit_assign_expr_inner(self, + def emit_assign_expr_inner(self, assignee_sym, assignee_fortran_name, assignee_subscript, expr, sym_kind): if assignee_subscript: subscript_str = "(%s)" % ( @@ -1729,6 +1716,7 @@ def emit_assign_expr_inner(self, subscript_str += "%" + sym_kind.identifier expr_kind = None + expression = self.expr(expr) if isinstance(expr, (Call, CallWithKwargs)): # These are supposed to have been transformed to AssignFunctionCall. @@ -1746,6 +1734,8 @@ def emit_assign_expr_inner(self, AssignmentEmitter(self)( ftype, assignee_fortran_name, {}, expr, is_rhs_target=True) + self.emit("") + return elif isinstance(sym_kind, UserTypeArray): ftype = self.get_fortran_type_for_user_type_array( sym_kind.identifier) @@ -1753,14 +1743,7 @@ def emit_assign_expr_inner(self, transformer = UserTypeArrayAppender( self, sym_kind.identifier) expression = self.expr(transformer(expr)) - self.emit( - "{name}{subscript_str} = {expr}" - .format( - name=assignee_fortran_name, - subscript_str=subscript_str, - expr=expression)) else: - expression = self.expr(expr) if isinstance(expr, Variable): expr_kind = self.sym_kind_table.get(self.current_function, expr.name) @@ -1836,41 +1819,23 @@ def emit_assign_expr_inner(self, self.expr(expr) + "(0)%array_size-1"), self) em.__enter__() - # Handle refcounts here as well - we are essentially - # doing per-element user type moves. - self.emit( - "{name}{subscript_str} => {expr}" - .format( - name=assignee_fortran_name, - subscript_str=subscript_str, - expr=expression)) - self.emit("{tgt_refcnt} => {refcnt}".format( - tgt_refcnt=tgt_refcnt_name, - refcnt=refcnt_name)) - self.emit("{tgt_refcnt} = {tgt_refcnt} + 1".format( - tgt_refcnt=tgt_refcnt_name)) + # Per-element user type moves. + assignee_loop_name = assignee_fortran_name \ + + subscript_str + self.emit_user_type_move(assignee_sym, + assignee_loop_name, + sym_kind, expression, + tgt_refcnt_name, refcnt_name, + deinit=False) self.emitter.__exit__(None, None, None) - else: - self.emit( - "{name}{subscript_str} = {expr}" - .format( - name=assignee_fortran_name, - subscript_str=subscript_str, - expr=expression)) - else: - self.emit( - "{name}{subscript_str} = {expr}" - .format( - name=assignee_fortran_name, - subscript_str=subscript_str, - expr=expression)) - else: - self.emit( - "{name}{subscript_str} = {expr}" - .format( - name=assignee_fortran_name, - subscript_str=subscript_str, - expr=self.expr(expr))) + self.emit("") + return + self.emit( + "{name}{subscript_str} = {expr}" + .format( + name=assignee_fortran_name, + subscript_str=subscript_str, + expr=expression)) self.emit("") @@ -2358,16 +2323,39 @@ def emit_assign_expr(self, assignee_sym, assignee_subscript, expr): if not isinstance(sym_kind, UserType): self.emit_assign_expr_inner( - assignee_fortran_name, assignee_subscript, expr, sym_kind) + assignee_sym, assignee_fortran_name, assignee_subscript, + expr, sym_kind) return if assignee_subscript: raise ValueError("User types do not support subscripting") + # Incorporate possibility for assigning UserTypeArray elements to + # UserTypes. + tgt_refcnt = self.name_manager.name_refcount(assignee_sym) if isinstance(expr, Variable): + expression = self.name_manager[expr.name] + refcnt = self.name_manager.name_refcount(expr.name) self.emit_user_type_move( - assignee_sym, assignee_fortran_name, sym_kind, expr) + assignee_sym, assignee_fortran_name, sym_kind, + expression, tgt_refcnt, refcnt) return + elif isinstance(expr, (Subscript, Lookup)): + expr_kind = self.sym_kind_table.get(self.current_function, + expr.aggregate.name) + if isinstance(expr_kind, UserTypeArray): + transformer = UserTypeArrayAppender( + self, sym_kind.identifier) + expression = self.expr(transformer(expr)) + refcnt = self.expr(expr) \ + + "%{}".format( + self.name_manager.name_refcount( + sym_kind.identifier, + qualified_with_state=False)) + self.emit_user_type_move( + assignee_sym, assignee_fortran_name, sym_kind, + expression, tgt_refcnt, refcnt) + return from pymbolic import var from pymbolic.mapper.dependency import DependencyMapper @@ -2379,7 +2367,8 @@ def emit_assign_expr(self, assignee_sym, assignee_subscript, expr): self.emit_allocation_check(assignee_sym, sym_kind) self.emit_assign_expr_inner( - assignee_fortran_name, assignee_subscript, expr, sym_kind) + assignee_sym, assignee_fortran_name, assignee_subscript, + expr, sym_kind) def lower_inst(self, inst): """Emit the code for an statement.""" From 2f926aed27ec5a19319329517f452451cc4db0dc Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Thu, 10 Jun 2021 11:49:50 -0500 Subject: [PATCH 18/21] Move UserTypeArray assignments to supporting function, a la UserType moves, to avoid cluttering emit_assign_expr_inner --- dagrt/codegen/fortran.py | 202 +++++++++++++++++++++------------------ 1 file changed, 108 insertions(+), 94 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index 4bed447..5b58cf2 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -1699,6 +1699,110 @@ def emit_user_type_move(self, assignee_sym, assignee_fortran_name, tgt_refcnt=tgt_refcnt)) self.emit("") + def emit_user_type_array_assignment(self, assignee_sym, assignee_fortran_name, + sym_kind, expr, subscript_str): + if (subscript_str or isinstance(expr, (Subscript, Lookup))): + transformer = UserTypeArrayAppender( + self, sym_kind.identifier) + expression = self.expr(transformer(expr)) + else: + if isinstance(expr, Variable): + expr_kind = self.sym_kind_table.get(self.current_function, + expr.name) + expression = self.expr(expr) + if isinstance(expr_kind, UserTypeArray): + # We need to loop through the UserTypeArray here, + # and attach the structure entrance to both the LHS and + # the RHS... + # For temps, we also need to check if the assignee is + # allocated, and if not, allocate it. + with FortranIfEmitter( + self.emitter, ".not.allocated({})".format( + assignee_fortran_name), self): + self.emit("allocate({}(0:{}-1))".format( + assignee_fortran_name, + self.expr(expr) + "(0)%array_size")) + alloc_check_name = self.get_alloc_check_name( + sym_kind.identifier) + ident = self.name_manager.make_unique_fortran_name( + "uarray_i") + self.declaration_emitter("integer %s" % ident) + em = FortranDoEmitter( + self.emitter, ident, + "0, {}".format( + self.expr(expr) + "(0)%array_size-1"), + self) + em.__enter__() + uarray_entry = assignee_fortran_name + \ + "(int({}))".format(ident) + self.emit( + "{out}%array_size = {existing}".format( + out=uarray_entry, + existing=self.expr(expr) + + "(0)%array_size")) + uarray_entry += "%{}".format(sym_kind.identifier) + refcnt_name = assignee_fortran_name \ + + "(int({}))".format(ident) + refcnt_name += "%{}".format( + self.name_manager.name_refcount( + sym_kind.identifier, + qualified_with_state=False)) + self.emit("allocate({})".format(refcnt_name)) + self.emit( + "call {alloc_check_name}({args})" + .format( + alloc_check_name=alloc_check_name, + args=", ".join( + self.extra_arguments + + (uarray_entry, refcnt_name)) + )) + self.emitter.__exit__(None, None, None) + ident = self.name_manager.make_unique_fortran_name( + "uarray_i") + expression = self.expr(expr) + \ + "({})%".format(ident) + expr_kind.identifier + subscript_str += "({})%".format(ident) \ + + expr_kind.identifier + tgt_refcnt_name = assignee_fortran_name \ + + "(int({}))".format(ident) + tgt_refcnt_name += "%{}".format( + self.name_manager.name_refcount( + sym_kind.identifier, + qualified_with_state=False)) + refcnt_name = self.expr(expr) \ + + "(int({}))".format(ident) + refcnt_name += "%{}".format( + self.name_manager.name_refcount( + sym_kind.identifier, + qualified_with_state=False)) + self.declaration_emitter("integer %s" % ident) + em = FortranDoEmitter( + self.emitter, ident, + "0, {}".format( + self.expr(expr) + "(0)%array_size-1"), + self) + em.__enter__() + # Per-element user type moves. + assignee_loop_name = assignee_fortran_name \ + + subscript_str + self.emit_user_type_move(assignee_sym, + assignee_loop_name, + sym_kind, expression, + tgt_refcnt_name, refcnt_name, + deinit=False) + self.emitter.__exit__(None, None, None) + self.emit("") + return + + self.emit( + "{name}{subscript_str} = {expr}" + .format( + name=assignee_fortran_name, + subscript_str=subscript_str, + expr=expression)) + + self.emit("") + def emit_assign_expr_inner(self, assignee_sym, assignee_fortran_name, assignee_subscript, expr, sym_kind): if assignee_subscript: @@ -1715,7 +1819,6 @@ def emit_assign_expr_inner(self, assignee_sym, if assignee_subscript: subscript_str += "%" + sym_kind.identifier - expr_kind = None expression = self.expr(expr) if isinstance(expr, (Call, CallWithKwargs)): @@ -1737,99 +1840,10 @@ def emit_assign_expr_inner(self, assignee_sym, self.emit("") return elif isinstance(sym_kind, UserTypeArray): - ftype = self.get_fortran_type_for_user_type_array( - sym_kind.identifier) - if (subscript_str or isinstance(expr, (Subscript, Lookup))): - transformer = UserTypeArrayAppender( - self, sym_kind.identifier) - expression = self.expr(transformer(expr)) - else: - if isinstance(expr, Variable): - expr_kind = self.sym_kind_table.get(self.current_function, - expr.name) - if isinstance(expr_kind, UserTypeArray): - # We need to loop through the UserTypeArray here, - # and attach the structure entrance to both the LHS and - # the RHS... - # For temps, we also need to check if the assignee is - # allocated, and if not, allocate it. - with FortranIfEmitter( - self.emitter, ".not.allocated({})".format( - assignee_fortran_name), self): - self.emit("allocate({}(0:{}-1))".format( - assignee_fortran_name, - self.expr(expr) + "(0)%array_size")) - alloc_check_name = self.get_alloc_check_name( - sym_kind.identifier) - ident = self.name_manager.make_unique_fortran_name( - "uarray_i") - self.declaration_emitter("integer %s" % ident) - em = FortranDoEmitter( - self.emitter, ident, - "0, {}".format( - self.expr(expr) + "(0)%array_size-1"), - self) - em.__enter__() - uarray_entry = assignee_fortran_name + \ - "(int({}))".format(ident) - self.emit( - "{out}%array_size = {existing}".format( - out=uarray_entry, - existing=self.expr(expr) - + "(0)%array_size")) - uarray_entry += "%{}".format(sym_kind.identifier) - refcnt_name = assignee_fortran_name \ - + "(int({}))".format(ident) - refcnt_name += "%{}".format( - self.name_manager.name_refcount( - sym_kind.identifier, - qualified_with_state=False)) - self.emit("allocate({})".format(refcnt_name)) - self.emit( - "call {alloc_check_name}({args})" - .format( - alloc_check_name=alloc_check_name, - args=", ".join( - self.extra_arguments - + (uarray_entry, refcnt_name)) - )) - self.emitter.__exit__(None, None, None) - ident = self.name_manager.make_unique_fortran_name( - "uarray_i") - expression = self.expr(expr) + \ - "({})%".format(ident) + expr_kind.identifier - subscript_str += "({})%".format(ident) \ - + expr_kind.identifier - tgt_refcnt_name = assignee_fortran_name \ - + "(int({}))".format(ident) - tgt_refcnt_name += "%{}".format( - self.name_manager.name_refcount( - sym_kind.identifier, - qualified_with_state=False)) - refcnt_name = self.expr(expr) \ - + "(int({}))".format(ident) - refcnt_name += "%{}".format( - self.name_manager.name_refcount( - sym_kind.identifier, - qualified_with_state=False)) - self.declaration_emitter("integer %s" % ident) - em = FortranDoEmitter( - self.emitter, ident, - "0, {}".format( - self.expr(expr) + "(0)%array_size-1"), - self) - em.__enter__() - # Per-element user type moves. - assignee_loop_name = assignee_fortran_name \ - + subscript_str - self.emit_user_type_move(assignee_sym, - assignee_loop_name, - sym_kind, expression, - tgt_refcnt_name, refcnt_name, - deinit=False) - self.emitter.__exit__(None, None, None) - self.emit("") - return + self.emit_user_type_array_assignment(assignee_sym, + assignee_fortran_name, + sym_kind, expr, subscript_str) + return self.emit( "{name}{subscript_str} = {expr}" .format( From 5a666d07bf78e7602131e0f9581e8084036d8789 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Fri, 11 Jun 2021 10:11:49 -0500 Subject: [PATCH 19/21] Add support for .ne. comparisons in Fortran --- dagrt/codegen/expressions.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/dagrt/codegen/expressions.py b/dagrt/codegen/expressions.py index 8a8b8d2..fa5e0c3 100644 --- a/dagrt/codegen/expressions.py +++ b/dagrt/codegen/expressions.py @@ -123,6 +123,24 @@ def map_logical_and(self, expr, enclosing_prec): " .and. ", expr.children, PREC_LOGICAL_AND), enclosing_prec, PREC_LOGICAL_AND) + _comparison_to_fortran = { + "==": ".eq.", + "!=": ".ne.", + "<": ".lt.", + ">": ".gt.", + "<=": ".le.", + ">=": ".ge.", + } + + def map_comparison(self, expr, enclosing_prec, *args, **kwargs): + from pymbolic.mapper.stringifier import PREC_COMPARISON + return self.parenthesize_if_needed( + self.format("%s %s %s", + self.rec(expr.left, PREC_COMPARISON, *args, **kwargs), + self._comparison_to_fortran[expr.operator], + self.rec(expr.right, PREC_COMPARISON, *args, **kwargs)), + enclosing_prec, PREC_COMPARISON) + # }}} From cd79c87963898b317e37c09d43d49fbad8b1498c Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Wed, 16 Jun 2021 11:34:02 -0500 Subject: [PATCH 20/21] Move end-of-loop deinits out of loops --- dagrt/codegen/fortran.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index 55251d8..e7ceb1e 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -1017,6 +1017,10 @@ def __init__(self, module_name, self.current_function = None self.used = False + self.in_loop = 0 + self.deinit_in_loop = False + self.deinit_emitter = FortranEmitter() + # }}} # {{{ utilities @@ -2316,10 +2320,18 @@ def emit_for_begin(self, loop_var_name, lbound, ubound): self.expr(lbound), self.expr(ubound-1)), code_generator=self) + self.in_loop += 1 em.__enter__() def emit_for_end(self, loop_var_name): self.emitter.__exit__(None, None, None) + self.in_loop -= 1 + # if we are out of all loops, emitter-juggle to + # perform any deinits we have hanging around + if self.deinit_in_loop: + if self.in_loop == 0: + self.emitter.incorporate(self.deinit_emitter) + self.deinit_in_loop = False def emit_assign_expr(self, assignee_sym, assignee_subscript, expr): from dagrt.data import UserType, Array @@ -2568,15 +2580,21 @@ def emit_deinit_for_last_usage_of_vars(self, inst): except KeyError: continue - # Defer deinit of these until the end of the function, - # in case they come up in loops. - if isinstance(var_kind, UserTypeArray): - continue - last_used_stmt_id = self.last_used_stmt_table[ variable, self.current_function] if inst.id == last_used_stmt_id and not is_state_variable(variable): - self.emit_variable_deinit(variable, var_kind) + # Check if we are in a loop or not. + if self.in_loop == 0: + self.emit_variable_deinit(variable, var_kind) + else: + # If we are in a loop, we need to pop the deinit outside + # of it. + self.deinit_emitter = FortranEmitter() + self.emitters.append(self.deinit_emitter) + self.emit_variable_deinit(variable, var_kind) + self.emit("") + self.deinit_in_loop = True + self.emitters.pop() def emit_inst_Raise(self, inst): # FIXME: Reenable emitting full error message From 4f0887df763a6786ff2c621d397f19ce2ee52cc4 Mon Sep 17 00:00:00 2001 From: Cory Mikida Date: Wed, 16 Jun 2021 11:43:14 -0500 Subject: [PATCH 21/21] Get rid of UserTypeArray hard dealloc --- dagrt/codegen/fortran.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/dagrt/codegen/fortran.py b/dagrt/codegen/fortran.py index e7ceb1e..c9ff62f 100644 --- a/dagrt/codegen/fortran.py +++ b/dagrt/codegen/fortran.py @@ -1217,9 +1217,6 @@ def lower_function(self, function_name, ast): for identifier, sym_kind in sorted(sym_table.items()): if (identifier, self.current_function) not in self.last_used_stmt_table: self.emit_variable_deinit(identifier, sym_kind) - # For now defer UserTypeArray dealloc until function end. - if isinstance(sym_kind, UserTypeArray): - self.emit_variable_deinit(identifier, sym_kind) # }}}