diff --git a/changelog b/changelog index 664c036f8b..34931cc34f 100644 --- a/changelog +++ b/changelog @@ -295,6 +295,9 @@ 112) PR #2795 for #2774. Generalises MatMul2CodeTrans to cope with non-contiguous array slices. + 113) PR #2769 for #2768. Generalises Sign2CodeTrans and Abs2CodeTrans + to allow non-floating point scalar literals. + release 2.5.0 14th of February 2024 1) PR #2199 for #2189. Fix bugs with missing maps in enter data diff --git a/psyclone.pdf b/psyclone.pdf index 531b84d76b..52aa970ead 100644 Binary files a/psyclone.pdf and b/psyclone.pdf differ diff --git a/src/psyclone/parse/algorithm.py b/src/psyclone/parse/algorithm.py index 18454f754e..4f71bcf921 100644 --- a/src/psyclone/parse/algorithm.py +++ b/src/psyclone/parse/algorithm.py @@ -687,8 +687,9 @@ def get_kernel(parse_tree, alg_filename, arg_type_defns): datatype = arg_type_defns.get(var_name) arguments.append(Arg('variable', full_text, varname=var_name, datatype=datatype)) - elif isinstance(argument, Part_Ref): - # An indexed variable e.g. arg(n) + elif isinstance(argument, (Part_Ref, Structure_Constructor)): + # An indexed variable e.g. arg(n). (Sometimes fparser matches + # these as structure constructors - fparser/#384.) full_text = argument.tostr().lower() var_name = str(argument.items[0]).lower() datatype = arg_type_defns.get(var_name) diff --git a/src/psyclone/psyad/domain/lfric/lfric_adjoint_harness.py b/src/psyclone/psyad/domain/lfric/lfric_adjoint_harness.py index 05dac9fab6..744ddec470 100644 --- a/src/psyclone/psyad/domain/lfric/lfric_adjoint_harness.py +++ b/src/psyclone/psyad/domain/lfric/lfric_adjoint_harness.py @@ -83,7 +83,7 @@ def _compute_lfric_inner_products(prog, scalars, field_sums, sum_sym): ''' table = prog.symbol_table idef_sym = table.add_lfric_precision_symbol("i_def") - idef_type = ScalarType(ScalarType.Intrinsic.REAL, idef_sym) + idef_type = ScalarType(ScalarType.Intrinsic.INTEGER, idef_sym) # Initialise the sum to zero: sum = 0.0 prog.addchild(Assignment.create(Reference(sum_sym), @@ -157,7 +157,7 @@ def _compute_field_inner_products(routine, field_pairs): rdef_sym = table.add_lfric_precision_symbol("r_def") rdef_type = ScalarType(ScalarType.Intrinsic.REAL, rdef_sym) idef_sym = table.add_lfric_precision_symbol("i_def") - idef_type = ScalarType(ScalarType.Intrinsic.REAL, idef_sym) + idef_type = ScalarType(ScalarType.Intrinsic.INTEGER, idef_sym) builtin_factory = LFRicBuiltinFunctorFactory.get() diff --git a/src/psyclone/psyir/backend/c.py b/src/psyclone/psyir/backend/c.py index babb36acd9..fb81da6af8 100644 --- a/src/psyclone/psyir/backend/c.py +++ b/src/psyclone/psyir/backend/c.py @@ -179,10 +179,15 @@ def literal_node(self, node): :rtype: str ''' - result = node.value - # C Scientific notation is always an 'e' letter - result = result.replace('d', 'e') - result = result.replace('D', 'e') + if node.datatype.intrinsic == ScalarType.Intrinsic.REAL: + try: + _ = int(node.value) + result = node.value + ".0" + except ValueError: + # It is already formatted as a real. + result = node.value + else: + result = node.value return result def ifblock_node(self, node): diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index f0ffa2560f..e42e77dc9c 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -1350,12 +1350,23 @@ def literal_node(self, node): ) quote_symbol = '"' result = f"{quote_symbol}{node.value}{quote_symbol}" - elif (node.datatype.intrinsic == ScalarType.Intrinsic.REAL and - precision == ScalarType.Precision.DOUBLE): - # The PSyIR stores real scalar values using the standard 'e' - # notation. If the scalar is in fact double precision then this - # 'e' must be replaced by 'd' for Fortran. - result = node.value.replace("e", "d", 1) + elif node.datatype.intrinsic == ScalarType.Intrinsic.REAL: + # Ensure it ends with ".0" if it isn't already explicitly + # formatted as a real. + result = node.value + try: + _ = int(result) + if precision == ScalarType.Precision.DOUBLE: + result = result + ".0d0" + else: + result = result + ".0" + except ValueError: + # It is already formatted as a real. + if precision == ScalarType.Precision.DOUBLE: + # The PSyIR stores real, scalar values using the standard + # 'e' notation. If the scalar is in fact double precision + # then this 'e' must be replaced by 'd' for Fortran. + result = result.replace("e", "d", 1) else: result = node.value diff --git a/src/psyclone/psyir/transformations/intrinsics/abs2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/abs2code_trans.py index 3714022b1c..e43156f8cb 100644 --- a/src/psyclone/psyir/transformations/intrinsics/abs2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/abs2code_trans.py @@ -44,7 +44,7 @@ Intrinsic2CodeTrans) from psyclone.psyir.nodes import ( BinaryOperation, Assignment, Reference, Literal, IfBlock, IntrinsicCall) -from psyclone.psyir.symbols import DataSymbol, REAL_TYPE +from psyclone.psyir.symbols import DataSymbol class Abs2CodeTrans(Intrinsic2CodeTrans): @@ -72,6 +72,19 @@ def __init__(self): super().__init__() self._intrinsic = IntrinsicCall.Intrinsic.ABS + def validate(self, node, options=None): + ''' + Check that it is safe to apply the transformation to the supplied node. + + :param node: the SIGN call to transform. + :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` + :param options: any of options for the transformation. + :type options: dict[str, Any] + + ''' + super().validate(node, options=options) + super()._validate_scalar_arg(node) + def apply(self, node, options=None): '''Apply the ABS intrinsic conversion transformation to the specified node. This node must be an ABS UnaryOperation. The ABS @@ -112,16 +125,12 @@ def apply(self, node, options=None): symbol_table = node.scope.symbol_table assignment = node.ancestor(Assignment) - # Create two temporary variables. There is an assumption here - # that the ABS Operator returns a PSyIR real type. This might - # not be what is wanted (e.g. the args might PSyIR integers), - # or there may be errors (arguments are of different types) - # but this can't be checked as we don't have the appropriate - # methods to query nodes (see #658). + # Create two temporary variables. + result_type = node.arguments[0].datatype symbol_res_var = symbol_table.new_symbol( - "res_abs", symbol_type=DataSymbol, datatype=REAL_TYPE) + "res_abs", symbol_type=DataSymbol, datatype=result_type) symbol_tmp_var = symbol_table.new_symbol( - "tmp_abs", symbol_type=DataSymbol, datatype=REAL_TYPE) + "tmp_abs", symbol_type=DataSymbol, datatype=result_type) # Replace operation with a temporary (res_X). node.replace_with(Reference(symbol_res_var)) @@ -134,7 +143,7 @@ def apply(self, node, options=None): # if condition: tmp_var>0.0 lhs = Reference(symbol_tmp_var) - rhs = Literal("0.0", REAL_TYPE) + rhs = Literal("0", result_type) if_condition = BinaryOperation.create(BinaryOperation.Operator.GT, lhs, rhs) @@ -146,7 +155,7 @@ def apply(self, node, options=None): # else_body: res_var=-1.0*tmp_var lhs = Reference(symbol_res_var) lhs_child = Reference(symbol_tmp_var) - rhs_child = Literal("-1.0", REAL_TYPE) + rhs_child = Literal("-1", result_type) rhs = BinaryOperation.create(BinaryOperation.Operator.MUL, lhs_child, rhs_child) else_body = [Assignment.create(lhs, rhs)] diff --git a/src/psyclone/psyir/transformations/intrinsics/intrinsic2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/intrinsic2code_trans.py index 5952d25f01..daebb82500 100644 --- a/src/psyclone/psyir/transformations/intrinsics/intrinsic2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/intrinsic2code_trans.py @@ -43,8 +43,9 @@ import abc from psyclone.psyGen import Transformation from psyclone.psyir.nodes import Assignment, IntrinsicCall -from psyclone.psyir.transformations.transformation_error import \ - TransformationError +from psyclone.psyir.symbols import ArrayType, ScalarType +from psyclone.psyir.transformations.transformation_error import ( + TransformationError) class Intrinsic2CodeTrans(Transformation, metaclass=abc.ABCMeta): @@ -95,3 +96,31 @@ def validate(self, node, options=None): f"Error in {self.name} transformation. This transformation " f"requires the operator to be part of an assignment " f"statement, but no such assignment was found.") + + def _validate_scalar_arg(self, node, options=None): + ''' + Check that the argument to the intrinsic is a scalar of known type. + + :param node: the target intrinsic call. + :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` + :param options: any options for the transformation. + :type options: dict[str, Any] + + :raises TransformationError: if the supplied SIGN call operates on + an argument of array type or unsupported/unresolved type. + + ''' + result_type = node.arguments[0].datatype + if isinstance(result_type, ArrayType): + raise TransformationError( + f"Transformation {self.name} cannot be applied to SIGN calls " + f"which have an array as argument but " + f"'{node.arguments[0].debug_string()}' is of array type. It " + f"may be possible to use the ArrayAssignment2LoopsTrans " + f"to convert this to a scalar argument.") + if not isinstance(result_type, ScalarType): + raise TransformationError( + f"Transformation {self.name} cannot be applied to " + f"'{node.debug_string()} because the type of the " + f"argument '{node.arguments[0].debug_string()}' is " + f"{result_type}") diff --git a/src/psyclone/psyir/transformations/intrinsics/sign2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/sign2code_trans.py index 620028aae1..215dd01c2d 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sign2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sign2code_trans.py @@ -45,7 +45,7 @@ from psyclone.psyir.transformations import Abs2CodeTrans from psyclone.psyir.nodes import ( BinaryOperation, Assignment, Reference, Literal, IfBlock, IntrinsicCall) -from psyclone.psyir.symbols import DataSymbol, REAL_TYPE +from psyclone.psyir.symbols import DataSymbol class Sign2CodeTrans(Intrinsic2CodeTrans): @@ -74,6 +74,19 @@ def __init__(self): super().__init__() self._intrinsic = IntrinsicCall.Intrinsic.SIGN + def validate(self, node, options=None): + ''' + Check that it is safe to apply the transformation to the supplied node. + + :param node: the SIGN call to transform. + :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` + :param options: any options for the transformation. + :type options: dict[str, Any] + + ''' + super().validate(node, options=options) + super()._validate_scalar_arg(node) + def apply(self, node, options=None): '''Apply the SIGN intrinsic conversion transformation to the specified node. This node must be a SIGN IntrinsicCall. The SIGN @@ -121,16 +134,14 @@ def apply(self, node, options=None): symbol_table = node.scope.symbol_table assignment = node.ancestor(Assignment) - # Create two temporary variables. There is an assumption here - # that the SIGN IntrinsicCall returns a PSyIR real type. This might - # not be what is wanted (e.g. the args might PSyIR integers), - # or there may be errors (arguments are of different types) - # but this can't be checked as we don't have the appropriate - # methods to query nodes (see #658). + # Create two temporary variables. We need to get the type of the + # first argument to SIGN. validate() has checked that this is a + # known, scalar type. + result_type = node.arguments[0].datatype res_var_symbol = symbol_table.new_symbol( - "res_sign", symbol_type=DataSymbol, datatype=REAL_TYPE) + "res_sign", symbol_type=DataSymbol, datatype=result_type) tmp_var_symbol = symbol_table.new_symbol( - "tmp_sign", symbol_type=DataSymbol, datatype=REAL_TYPE) + "tmp_sign", symbol_type=DataSymbol, datatype=result_type) # Replace operator with a temporary (res_var). node.replace_with(Reference(res_var_symbol)) @@ -155,14 +166,14 @@ def apply(self, node, options=None): # if_condition: tmp_var<0.0 lhs = Reference(tmp_var_symbol) - rhs = Literal("0.0", REAL_TYPE) + rhs = Literal("0", result_type) if_condition = BinaryOperation.create(BinaryOperation.Operator.LT, lhs, rhs) # then_body: res_var=res_var*-1.0 lhs = Reference(res_var_symbol) lhs_child = Reference(res_var_symbol) - rhs_child = Literal("-1.0", REAL_TYPE) + rhs_child = Literal("-1", result_type) rhs = BinaryOperation.create(BinaryOperation.Operator.MUL, lhs_child, rhs_child) then_body = [Assignment.create(lhs, rhs)] diff --git a/src/psyclone/tests/psyir/backend/c_test.py b/src/psyclone/tests/psyir/backend/c_test.py index 891b229679..507f5b193e 100644 --- a/src/psyclone/tests/psyir/backend/c_test.py +++ b/src/psyclone/tests/psyir/backend/c_test.py @@ -143,6 +143,9 @@ def test_cw_literal(): lit = Literal('1', INTEGER_TYPE) assert cwriter(lit) == '1' + lit = Literal('1', REAL_TYPE) + assert cwriter(lit) == '1.0' + # Test that scientific notation is output correctly lit = Literal("3e5", REAL_TYPE, None) assert cwriter(lit) == '3e5' diff --git a/src/psyclone/tests/psyir/backend/fortran_test.py b/src/psyclone/tests/psyir/backend/fortran_test.py index 553bab72a5..32c6b38074 100644 --- a/src/psyclone/tests/psyir/backend/fortran_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_test.py @@ -1393,6 +1393,7 @@ def test_fw_char_literal(fortran_writer): result = fortran_writer(lit) assert result == "'hello'" + # literal is already checked within previous tests @@ -1687,6 +1688,10 @@ def test_fw_literal_node(fortran_writer): result = fortran_writer(lit1) assert result == '3.14' + lit1 = Literal('3', REAL_TYPE) + result = fortran_writer(lit1) + assert result == '3.0' + lit1 = Literal('3.14E0', REAL_TYPE) result = fortran_writer(lit1) assert result == '3.14e0' @@ -1695,6 +1700,10 @@ def test_fw_literal_node(fortran_writer): result = fortran_writer(lit1) assert result == '3.14d0' + lit1 = Literal('3', REAL_DOUBLE_TYPE) + result = fortran_writer(lit1) + assert result == '3.0d0' + # Check that BOOLEANS use the FORTRAN formatting lit1 = Literal('true', BOOLEAN_TYPE) result = fortran_writer(lit1) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/sign2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/sign2code_trans_test.py index c7be600548..d4df2eb227 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/sign2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/sign2code_trans_test.py @@ -191,7 +191,7 @@ def test_correct_expr(tmpdir): assert Compile(tmpdir).string_compiles(result) -def test_correct_2sign(tmpdir): +def test_correct_2sign(tmpdir, fortran_writer): '''Check that a valid example produces the expected output when there is more than one SIGN in an expression. @@ -205,12 +205,11 @@ def test_correct_2sign(tmpdir): intr_call.detach() intr_call2 = IntrinsicCall.create( IntrinsicCall.Intrinsic.SIGN, - [Literal("1.0", REAL_TYPE), Literal("1.0", REAL_TYPE)]) + [Literal("1", REAL_TYPE), Literal("1", REAL_TYPE)]) op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, intr_call2, intr_call) assignment.addchild(op1) - writer = FortranWriter() - result = writer(root) + result = fortran_writer(root) assert ( "subroutine sign_example(arg, arg_1)\n" " real, intent(inout) :: arg\n" @@ -221,7 +220,7 @@ def test_correct_2sign(tmpdir): trans = Sign2CodeTrans() trans.apply(intr_call, root.symbol_table) trans.apply(intr_call2, root.symbol_table) - result = writer(root) + result = fortran_writer(root) assert ( "subroutine sign_example(arg, arg_1)\n" " real, intent(inout) :: arg\n" @@ -262,6 +261,64 @@ def test_correct_2sign(tmpdir): assert Compile(tmpdir).string_compiles(result) +def test_sign_with_integer_arg(fortran_reader, fortran_writer, tmpdir): + ''' + Test that the transformation works when the SIGN argument is an + integer. + + ''' + code = '''\ + program test_prog + integer, parameter :: idef = kind(1) + integer(idef) :: my_arg, other_arg + my_arg = SIGN(my_arg, other_arg) + end program test_prog''' + psyir = fortran_reader.psyir_from_source(code) + trans = Sign2CodeTrans() + sgn_call = psyir.walk(IntrinsicCall)[0] + trans.apply(sgn_call) + result = fortran_writer(psyir) + assert "integer(kind=idef) :: res_abs" in result + assert "integer(kind=idef) :: tmp_abs" in result + assert "if (tmp_abs > 0_idef) then" in result + assert "res_abs = tmp_abs * -1_idef" in result + assert Compile(tmpdir).string_compiles(result) + + +def test_sign_of_unknown_type(fortran_reader): + ''' + Check that we refuse to apply the transformation if the argument + is of array or unknown type. + + ''' + code = '''\ + program test_prog + integer, parameter :: wp = kind(1.0d0) + integer, parameter, dimension(0:4) :: A2D = (/1, 2, 3, 4, 5/) + REAL(wp), DIMENSION(A2D(0)) :: ztmp1 + ztmp1 = 0.0 + ! Can't handle because we don't know the type of MAX or ABS + ztmp1 = SIGN( MAX(ABS(ztmp1),1.E-6_wp), ztmp1 ) + ! Can't handle because ztmp1 is an array + ztmp1 = SIGN( ztmp1, 1.0 ) + end program test_prog''' + psyir = fortran_reader.psyir_from_source(code) + trans = Sign2CodeTrans() + sgn_calls = [call for call in psyir.walk(IntrinsicCall) + if call.intrinsic.name == "SIGN"] + with pytest.raises(TransformationError) as err: + trans.validate(sgn_calls[0]) + assert ("Sign2CodeTrans cannot be applied to 'SIGN(MAX(ABS(ztmp1), " + "1.e-6_wp), ztmp1) because the type of the argument" + in str(err.value)) + with pytest.raises(TransformationError) as err: + trans.validate(sgn_calls[1]) + assert ("Sign2CodeTrans cannot be applied to SIGN calls which have an " + "array as argument but 'ztmp1' is of array type. It may be " + "possible to use the ArrayAssignment2LoopsTrans to convert this " + "to a scalar argument" in str(err.value)) + + def test_invalid(): '''Check that the validate tests are run when the apply method is called.'''