diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml index aec7fc05b7..88abad065e 100644 --- a/.github/workflows/compilation.yml +++ b/.github/workflows/compilation.yml @@ -90,7 +90,7 @@ jobs: module load python/${PYTHON_VERSION} . .runner_venv/bin/activate module load gcc/${GFORTRAN_VERSION} openmpi netcdf_fortran - pytest -n 2 --f90=gfortran --compile --compileopencl src/psyclone/tests + pytest -n 2 --f90=gfortran --f90flags="-ffree-line-length-none" --compile --compileopencl src/psyclone/tests module rm netcdf_fortran gcc - name: Unit tests with compilation - nvfortran run: | @@ -108,7 +108,7 @@ jobs: # Although we're using gfortran, we link with the OpenCL lib that comes # with CUDA. make -C examples allclean - F90=gfortran F90FLAGS="-L/apps/packages/compilers/nvidia-hpcsdk/Linux_x86_64/${NVFORTRAN_VERSION}/cuda/${CUDA_VERSION}/targets/x86_64-linux/lib" make -C examples compile + F90=gfortran F90FLAGS="-ffree-line-length-none -L/apps/packages/compilers/nvidia-hpcsdk/Linux_x86_64/${NVFORTRAN_VERSION}/cuda/${CUDA_VERSION}/targets/x86_64-linux/lib" make -C examples compile - name: Tutorials with compilation - gfortran run: | module load python/${PYTHON_VERSION} diff --git a/changelog b/changelog index a89893da83..b820488ce9 100644 --- a/changelog +++ b/changelog @@ -596,6 +596,8 @@ 201) PR #2148 towards #2105. Adds {Min,Max}Val2Code transformations. + 202) PR 2298 for #1987. Change all intrinsic Operations to IntrinsicCalls. + release 2.3.1 17th of June 2022 1) PR #1747 for #1720. Adds support for If blocks to PSyAD. diff --git a/doc/developer_guide/psyir.rst b/doc/developer_guide/psyir.rst index 7e11f88099..88df59cb19 100644 --- a/doc/developer_guide/psyir.rst +++ b/doc/developer_guide/psyir.rst @@ -497,9 +497,9 @@ See the full Range API in the Operation Nodes --------------- -Arithmetic operations and various intrinsic/query functions are represented -in the PSyIR by sub-classes of the `Operation` node. The operations are -classified according to the number of operands: +Arithmetic and logic operations are represented in the PSyIR by sub-classes +of the `Operation` node. The operations are classified according to the number +of operands: - Those having one operand are represented by `psyclone.psyir.nodes.UnaryOperation` nodes, @@ -507,27 +507,12 @@ classified according to the number of operands: - those having two operands are represented by `psyclone.psyir.nodes.BinaryOperation` nodes. -- and those having more than two or a variable number of operands are - represented by `psyclone.psyir.nodes.NaryOperation` nodes. - See the documentation for each Operation class in the :ref_guide:`Operation psyclone.psyir.nodes.html#psyclone.psyir.nodes.Operation`, -:ref_guide:`UnaryOperation psyclone.psyir.nodes.html#psyclone.psyir.nodes.UnaryOperation`, -:ref_guide:`BinaryOperation psyclone.psyir.nodes.html#psyclone.psyir.nodes.BinaryOperation` and -:ref_guide:`NaryOperation psyclone.psyir.nodes.html#psyclone.psyir.nodes.NaryOperation` +:ref_guide:`UnaryOperation psyclone.psyir.nodes.html#psyclone.psyir.nodes.UnaryOperation` and +:ref_guide:`BinaryOperation psyclone.psyir.nodes.html#psyclone.psyir.nodes.BinaryOperation` sections of the reference guide. -Note that where an intrinsic (such as -Fortran's `MAX`) can have a variable number of arguments, the class -used to represent it in the PSyIR is determined by the actual number -of arguments in a particular instance. e.g. `MAX(var1, var2)` would be -represented by a `psyclone.psyir.nodes.BinaryOperation` but `MAX(var1, -var2, var3)` would be represented by a -`psyclone.psyir.nodes.NaryOperation`. - -The PSyIR supports the concept of named arguments for operation -nodes, see the :ref:`named_arguments-label` section for more details. - .. note:: Similar to Fortran, the PSyIR has two comparison operators, one for booleans (EQV) and one for non-booleans (EQ). These are not interchangeable because they have different precedence priorities and @@ -544,57 +529,16 @@ IntrinsicCall Nodes PSyIR `IntrinsicCall` nodes (see :ref_guide:`IntrinsicCall psyclone.psyir.nodes.html#psyclone.psyir.nodes.IntrinsicCall`) capture all PSyIR intrinsics that are not expressed as language symbols (`+`,`-`,`*` -etc). The latter are captured as `Operation` nodes. At the moment some -intrinsics that should be captured as `IntrinsicCall` nodes are -captured as `Operation` nodes (for example `sin` and `cos`). These -will be migrated to being captured as `IntrinsicCall` nodes in the -near future. Supported `IntrinsicCall` intrinsics are listed in the -`IntrinsicCall.Intrinsic` enumeration within the class: - -+--------------+------------------------------+--------------------------------+ -| Name | Positional arguments | Optional arguments | -+--------------+------------------------------+------+-------------------------+ -| ALLOCATE | One or more Reference or | stat | Reference to an integer | -| | ArrayReferences to which | | variable which will hold| -| | memory will be allocated. | | return status. | -| | +------+-------------------------+ -| | | mold | Reference to an array | -| | | | which is used to specify| -| | | | the dimensions of the | -| | | | allocated object. | -| | +------+-------------------------+ -| | |source| Reference to an array | -| | | | which is used to specify| -| | | | both the dimensions & | -| | | | initial value(s) of the | -| | | | allocated object. | -| | +------+-------------------------+ -| | |errmsg| Reference to a character| -| | | | variable which will | -| | | | contain an error message| -| | | | should the operation | -| | | | fail. | -+--------------+------------------------------+------+-------------------------+ -| DEALLOCATE | One or more References. | stat | Reference which will | -| | | | hold return status. | -+--------------+------------------------------+------+-------------------------+ -| RANDOM_NUMBER| A single Reference which will| | -| | be filled with pseudo-random | | -| | numbers in the range | | -| | [0.0, 1.0]. | | -+--------------+------------------------------+------+-------------------------+ -| SUM, MAXVAL, | A single DataNode. | dim | A DataNode specifying | -| MINVAL | | | one of the supplied | -| | | | array's dimensions. | -| | +------+-------------------------+ -| | | mask | A DataNode indicating | -| | | | on which elements to | -| | | | apply the function. | -+--------------+------------------------------+------+-------------------------+ -| HUGE, TINY | A single Reference or | | -| | Literal. | | -+--------------+------------------------------+--------------------------------+ +etc). The latter are captured as `Operation` nodes. At the moment the +available PSyIR `IntrinsicCall` match those of the `Fortran 2018 standard +`_ +In addition to Fortran Intrinsics, special Fortran statements such as: +`ALLOCATE`, `DEALLOCATE` and `NULLIFY` are also PSyIR IntrinsicCalls. + +IntrinsicCalls, like Calls, have properties to inform if the call is to a +pure, elemental, inquiry (does not touch the first argument data) function +or is available on a GPU device. CodeBlock Node -------------- @@ -702,9 +646,7 @@ psyclone.psyir.nodes.html#psyclone.psyir.nodes.Directive`. Named arguments --------------- -The `Call` node and the three subclasses of the `Operation` node -(`UnaryOperation`, `BinaryOperation` and `NaryOperation`) all support -named arguments. +The `Call` node (and its sub-classes) support named arguments. The argument names are provided by the `argument_names` property. This property returns a list of names. The first entry in the list refers @@ -752,10 +694,9 @@ re-ordered; an argument that has replaced a named argument will not be a named argument; an inserted argument will not be a named argument, and the name of a deleted named argument will be removed. -Making a copy of the `Call` node or one of the three subclasses of -Operation nodes (`UnaryOperation`, `BinaryOperation` or -`NaryOperation`) also causes problems with consistency between the -internal `_argument_names` list and the arguments. The reason for this +Making a copy of the `Call` node also causes problems with consistency +between the internal `_argument_names` list and the arguments. +The reason for this is that the arguments get copied and therefore have a different `id`, whereas the `id`s in the internal `_argument_names` list are simply copied. To solve this problem, the `copy()` method is specialised to diff --git a/doc/user_guide/psyir.rst b/doc/user_guide/psyir.rst index 2d2dd48903..35b9965c14 100644 --- a/doc/user_guide/psyir.rst +++ b/doc/user_guide/psyir.rst @@ -109,7 +109,7 @@ PSyIR nodes are: ``Loop``, ``WhileLoop``, ``IfBlock``, ``CodeBlock``, subclassed into ``ArrayReference``, ``StructureReference`` and ``ArrayOfStructuresReference``, the ``Operation`` class is further subclassed into ``UnaryOperation``, ``BinaryOperation`` and -``NaryOperation`` and the ``Container`` class is further subclassed +the ``Container`` class is further subclassed into ``FileContainer`` (representing a file that may contain more than one ``Container`` and/or ``Routine``. Those nodes representing references to structures (derived types in Fortran) have a ``Member`` @@ -780,9 +780,7 @@ parent reference are not. Named arguments --------------- -The `Call` and three sub-classes of `Operation` node -(`UnaryOperation`, `BinaryOperation` and `NaryOperation`) all support -named arguments. +The `Call` node (and its sub-classes) support named arguments. Named arguments can be set or modified via the `create()`, `append_named_arg()`, `insert_named_arg()` or `replace_named_arg()` diff --git a/doc/user_guide/transformations.rst b/doc/user_guide/transformations.rst index 8f1f95e105..dc2ba67055 100644 --- a/doc/user_guide/transformations.rst +++ b/doc/user_guide/transformations.rst @@ -147,7 +147,7 @@ can be found in the API-specific sections). :members: apply :noindex: -.. warning:: This transformation assumes that the ABS Operator acts on +.. warning:: This transformation assumes that the ABS Intrinsic acts on PSyIR Real scalar data and does not check that this is not the case. Once issue #658 is on master then this limitation can be fixed. @@ -275,7 +275,7 @@ can be found in the API-specific sections). :members: apply :noindex: -.. warning:: This transformation assumes that the MAX Operator acts on +.. warning:: This transformation assumes that the MAX Intrinsic acts on PSyIR Real scalar data and does not check that this is not the case. Once issue #658 is on master then this limitation can be fixed. @@ -292,7 +292,7 @@ can be found in the API-specific sections). :members: apply :noindex: -.. warning:: This transformation assumes that the MIN Operator acts on +.. warning:: This transformation assumes that the MIN Intrinsic acts on PSyIR Real scalar data and does not check that this is not the case. Once issue #658 is on master then this limitation can be fixed. @@ -436,7 +436,7 @@ can be found in the API-specific sections). :members: apply :noindex: -.. warning:: This transformation assumes that the SIGN Operator acts +.. warning:: This transformation assumes that the SIGN Intrinsic acts on PSyIR Real scalar data and does not check whether or not this is the case. Once issue #658 is on master then this limitation can be fixed. diff --git a/examples/lfric/eg15/matvec_opt.py b/examples/lfric/eg15/matvec_opt.py index 08ccdc21ac..4a32d2a204 100644 --- a/examples/lfric/eg15/matvec_opt.py +++ b/examples/lfric/eg15/matvec_opt.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -31,8 +31,8 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -# Author R. W. Ford STFC Daresbury Lab. - +# Author: R. W. Ford, STFC Daresbury Lab. +# Modified: S. Siso, STFC Daresbury Lab. '''An example PSyclone transformation script to demonstrate optimisations to the matrix vector kernel to improve its performance @@ -68,9 +68,8 @@ -oalg /dev/null -opsy /dev/null ''' -from __future__ import print_function import sys -from psyclone.psyir.nodes import BinaryOperation +from psyclone.psyir.nodes import IntrinsicCall from psyclone.psyir.transformations import Matmul2CodeTrans from psyclone.psyir.backend.fortran import FortranWriter @@ -93,9 +92,9 @@ def trans(psy): if kernel.name.lower() == "matrix_vector_kernel_code": kernel_schedule = kernel.get_kernel_schedule() # Replace matmul with inline code - for bin_op in kernel_schedule.walk(BinaryOperation): - if bin_op.operator is BinaryOperation.Operator.MATMUL: - matmul2code_trans.apply(bin_op) + for icall in kernel_schedule.walk(IntrinsicCall): + if icall.intrinsic is IntrinsicCall.Intrinsic.MATMUL: + matmul2code_trans.apply(icall) # Future optimisations will go here. print(kernel_schedule.view()) result = fortran_writer(kernel_schedule) diff --git a/examples/lfric/scripts/everything_everywhere_all_at_once.py b/examples/lfric/scripts/everything_everywhere_all_at_once.py index e781f069fc..f61e81bcdc 100644 --- a/examples/lfric/scripts/everything_everywhere_all_at_once.py +++ b/examples/lfric/scripts/everything_everywhere_all_at_once.py @@ -43,7 +43,7 @@ from psyclone.domain.lfric import LFRicConstants from psyclone.dynamo0p3 import DynHaloExchange, DynHaloExchangeStart from psyclone.psyir.transformations import Matmul2CodeTrans -from psyclone.psyir.nodes import BinaryOperation, Container, KernelSchedule +from psyclone.psyir.nodes import IntrinsicCall, Container, KernelSchedule from psyclone.transformations import Dynamo0p3ColourTrans, \ Dynamo0p3OMPLoopTrans, \ OMPParallelTrans, \ @@ -133,10 +133,10 @@ def trans(psy): for kschedule in root.walk(KernelSchedule): if ENABLE_INTRINSIC_INLINING: # Expand MATMUL intrinsic - for bop in kschedule.walk(BinaryOperation): - if bop.operator == BinaryOperation.Operator.MATMUL: + for icall in kschedule.walk(IntrinsicCall): + if icall.intrinsic == IntrinsicCall.Intrinsic.MATMUL: try: - matmul_trans.apply(bop) + matmul_trans.apply(icall) except TransformationError: pass diff --git a/examples/lfric/scripts/inline_kernels_and_intrinsics.py b/examples/lfric/scripts/inline_kernels_and_intrinsics.py index 5d3ccdb737..3b65c53d55 100644 --- a/examples/lfric/scripts/inline_kernels_and_intrinsics.py +++ b/examples/lfric/scripts/inline_kernels_and_intrinsics.py @@ -39,7 +39,7 @@ ''' from psyclone.domain.common.transformations import KernelModuleInlineTrans -from psyclone.psyir.nodes import BinaryOperation, Container, KernelSchedule +from psyclone.psyir.nodes import IntrinsicCall, Container, KernelSchedule from psyclone.psyir.transformations import Matmul2CodeTrans from psyclone.transformations import TransformationError @@ -70,10 +70,10 @@ def trans(psy): root = psy.invokes.invoke_list[0].schedule.ancestor(Container) for kschedule in root.walk(KernelSchedule): # Expand MATMUL intrinsic - for bop in kschedule.walk(BinaryOperation): - if bop.operator == BinaryOperation.Operator.MATMUL: + for icall in kschedule.walk(IntrinsicCall): + if icall.intrinsic == IntrinsicCall.Intrinsic.MATMUL: try: - matmul_trans.apply(bop) + matmul_trans.apply(icall) except TransformationError as err: print(f"Inline MATMUL failed for '{kschedule.name}' " "because:") diff --git a/examples/nemo/eg4/sir_trans_all.py b/examples/nemo/eg4/sir_trans_all.py index fd1be06eaa..a9f6e5ad21 100644 --- a/examples/nemo/eg4/sir_trans_all.py +++ b/examples/nemo/eg4/sir_trans_all.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2021, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,6 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: R. W. Ford, STFC Daresbury Lab +# Modified: S. Siso, STFC Daresbury Lab '''Module providing a transformation script that converts the supplied PSyIR to the Stencil intermediate representation (SIR) and @@ -48,12 +49,10 @@ the original code is translated. ''' -from __future__ import print_function from psyclone.psyir.backend.sir import SIRWriter from psyclone.psyir.backend.fortran import FortranWriter from psyclone.nemo import NemoKern -from psyclone.psyir.nodes import (UnaryOperation, BinaryOperation, - NaryOperation, Operation, Assignment) +from psyclone.psyir.nodes import IntrinsicCall, Assignment from psyclone.psyir.transformations import Abs2CodeTrans, Sign2CodeTrans, \ Min2CodeTrans, Max2CodeTrans, HoistTrans from psyclone.domain.nemo.transformations import NemoAllArrayRange2LoopTrans, \ @@ -101,21 +100,19 @@ def trans(psy): for kernel in schedule.walk(NemoKern): kernel_schedule = kernel.get_kernel_schedule() - for oper in kernel_schedule.walk(Operation): - if oper.operator == UnaryOperation.Operator.ABS: + for icall in kernel_schedule.walk(IntrinsicCall): + if icall.intrinsic == IntrinsicCall.Intrinsic.ABS: # Apply ABS transformation - abs_trans.apply(oper) - elif oper.operator == BinaryOperation.Operator.SIGN: + abs_trans.apply(icall) + elif icall.intrinsic == IntrinsicCall.Intrinsic.SIGN: # Apply SIGN transformation - sign_trans.apply(oper) - elif oper.operator in [BinaryOperation.Operator.MIN, - NaryOperation.Operator.MIN]: + sign_trans.apply(icall) + elif icall.intrinsic == IntrinsicCall.Intrinsic.MIN: # Apply (2-n arg) MIN transformation - min_trans.apply(oper) - elif oper.operator in [BinaryOperation.Operator.MAX, - NaryOperation.Operator.MAX]: + min_trans.apply(icall) + elif icall.intrinsic in IntrinsicCall.Intrinsic.MAX: # Apply (2-n arg) MAX transformation - max_trans.apply(oper) + max_trans.apply(icall) # Remove any loop invariant assignments inside k-loops to make # them perfectly nested. At the moment this transformation diff --git a/examples/nemo/scripts/kernels_trans.py b/examples/nemo/scripts/kernels_trans.py index d86ebbf17b..fee23e91fc 100755 --- a/examples/nemo/scripts/kernels_trans.py +++ b/examples/nemo/scripts/kernels_trans.py @@ -188,6 +188,9 @@ def valid_acc_kernel(node): excluded_nodes = node.walk(excluded_types) for enode in excluded_nodes: + if isinstance(enode, Call) and enode.is_available_on_device(): + continue + if isinstance(enode, (CodeBlock, Return, Call, WhileLoop)): log_msg(routine_name, f"region contains {type(enode).__name__}", enode) @@ -405,11 +408,13 @@ def trans(psy): continue # In the lib_fortran file we annotate each routine that does not - # have a Loop or a Call with the OpenACC Routine Directive - if psy.name == "psy_lib_fortran_psy" and not sched.walk((Loop, Call)): - print(f"Transforming {invoke.name} with acc routine") - ACC_ROUTINE_TRANS.apply(sched) - continue + # have a Loop or unsupported Calls with the OpenACC Routine Directive + if psy.name == "psy_lib_fortran_psy": + if not invoke.schedule.walk(Loop): + calls = invoke.schedule.walk(Call) + if all(call.is_available_on_device() for call in calls): + ACC_ROUTINE_TRANS.apply(sched) + continue # Attempt to add OpenACC directives unless we are ignoring this routine if invoke.name.lower() not in ACC_IGNORE: diff --git a/examples/nemo/scripts/omp_cpu_trans.py b/examples/nemo/scripts/omp_cpu_trans.py index 4489691257..834a78dc18 100755 --- a/examples/nemo/scripts/omp_cpu_trans.py +++ b/examples/nemo/scripts/omp_cpu_trans.py @@ -66,6 +66,12 @@ def trans(psy): if PROFILING_ENABLED: add_profiling(invoke.schedule.children) + # TODO #2317: Has structure accesses that can not be offloaded and has + # a problematic range to loop expansion of (1:1) + if psy.name.startswith("psy_obs_"): + print("Skipping", invoke.name) + continue + enhance_tree_information(invoke.schedule) if invoke.name in ("eos_rprof"): diff --git a/examples/nemo/scripts/omp_gpu_trans.py b/examples/nemo/scripts/omp_gpu_trans.py index f69b2200b0..bbe6f608dd 100755 --- a/examples/nemo/scripts/omp_gpu_trans.py +++ b/examples/nemo/scripts/omp_gpu_trans.py @@ -2,7 +2,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2021-2022, Science and Technology Facilities Council. +# Copyright (c) 2021-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -37,12 +37,13 @@ ''' PSyclone transformation script showing the introduction of OpenMP for GPU directives into Nemo code. ''' +from utils import insert_explicit_loop_parallelism, normalise_loops, \ + enhance_tree_information, add_profiling + from psyclone.psyGen import TransInfo from psyclone.psyir.nodes import Call, Loop from psyclone.psyir.transformations import OMPTargetTrans from psyclone.transformations import OMPDeclareTargetTrans -from utils import insert_explicit_loop_parallelism, normalise_loops, \ - enhance_tree_information, add_profiling PROFILING_ENABLED = True @@ -69,7 +70,8 @@ def trans(psy): if PROFILING_ENABLED: add_profiling(invoke.schedule.children) - # Has structure accesses that can not be offloaded + # TODO #2317: Has structure accesses that can not be offloaded and has + # a problematic range to loop expansion of (1:1) if psy.name.startswith("psy_obs_"): print("Skipping", invoke.name) continue @@ -107,9 +109,11 @@ def trans(psy): # For performance in lib_fortran, mark serial routines as GPU-enabled if psy.name == "psy_lib_fortran_psy": - if not invoke.schedule.walk((Loop, Call)): - OMPDeclareTargetTrans().apply(invoke.schedule) - continue + if not invoke.schedule.walk(Loop): + calls = invoke.schedule.walk(Call) + if all(call.is_available_on_device() for call in calls): + OMPDeclareTargetTrans().apply(invoke.schedule) + continue insert_explicit_loop_parallelism( invoke.schedule, diff --git a/examples/nemo/scripts/utils.py b/examples/nemo/scripts/utils.py index 7b464827d4..fcff5e7683 100755 --- a/examples/nemo/scripts/utils.py +++ b/examples/nemo/scripts/utils.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2022, Science and Technology Facilities Council. +# Copyright (c) 2022-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -36,7 +36,7 @@ ''' Utilities file to parallelise Nemo code. ''' from psyclone.psyir.nodes import Loop, Assignment, Directive, Container, \ - Reference, CodeBlock, Call, Return, IfBlock, Routine, BinaryOperation, \ + Reference, CodeBlock, Call, Return, IfBlock, Routine, \ IntrinsicCall from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE, REAL_TYPE, \ ArrayType, ScalarType, RoutineSymbol, ImportInterface @@ -55,12 +55,6 @@ "interp1", "interp2", "interp3", "integ_spline", "sbc_dcy", "sum", "sign_", "ddpdd"] -# From: https://docs.nvidia.com/hpc-sdk/compilers/hpc-compilers-user-guide/ -# index.html#acc-fort-intrin-sum -NVIDIA_GPU_SUPPORTED_INTRINSICS = [ - IntrinsicCall.Intrinsic.SUM, -] - VERBOSE = False @@ -163,6 +157,10 @@ def normalise_loops( if convert_array_notation: # Make sure all array dimensions are explicit for reference in schedule.walk(Reference, stop_type=Reference): + part_of_the_call = reference.ancestor(Call) + if part_of_the_call: + if not part_of_the_call.is_elemental: + continue if isinstance(reference.symbol, DataSymbol): try: Reference2ArrayRangeTrans().apply(reference) @@ -238,49 +236,49 @@ def insert_explicit_loop_parallelism( # In addition, they often nest ice linearised loops (npti) # which we'd rather parallelise if ('ice' in routine_name - and isinstance(loop.stop_expr, BinaryOperation) - and (loop.stop_expr.operator == BinaryOperation.Operator.UBOUND or - loop.stop_expr.operator == BinaryOperation.Operator.SIZE) + and isinstance(loop.stop_expr, IntrinsicCall) + and (loop.stop_expr.intrinsic in (IntrinsicCall.Intrinsic.UBOUND, + IntrinsicCall.Intrinsic.SIZE)) and (len(loop.walk(Loop)) > 2 - or any([ref.symbol.name in ('npti',) - for lp in loop.loop_body.walk(Loop) - for ref in lp.stop_expr.walk(Reference)]) + or any(ref.symbol.name in ('npti',) + for lp in loop.loop_body.walk(Loop) + for ref in lp.stop_expr.walk(Reference)) or (str(len(loop.walk(Loop))) != loop.stop_expr.children[1].value))): print("ICE Loop not parallelised for performance reasons") continue # Skip if looping over ice categories, ice or snow layers # as these have only 5, 4, and 1 iterations, respectively - if (any([ref.symbol.name in ('jpl', 'nlay_i', 'nlay_s') - for ref in loop.stop_expr.walk(Reference)])): + if (any(ref.symbol.name in ('jpl', 'nlay_i', 'nlay_s') + for ref in loop.stop_expr.walk(Reference))): print("Loop not parallelised because stops at 'jpl', 'nlay_i' " "or 'nlay_s'.") continue - def skip_for_correctness(): + def skip_for_correctness(loop): for call in loop.walk(Call): if not isinstance(call, IntrinsicCall): print(f"Loop not parallelised because it has a call to " f"{call.routine.name}") return True - if call.intrinsic not in NVIDIA_GPU_SUPPORTED_INTRINSICS: + if not call.is_available_on_device(): print(f"Loop not parallelised because it has a " - f"{call.intrinsic.name}") + f"{call.intrinsic.name} not available on GPUs.") return True if loop.walk(CodeBlock): - print(f"Loop not parallelised because it has a CodeBlock") + print("Loop not parallelised because it has a CodeBlock") return True return False # If we see one such ice linearised loop, we assume # calls/codeblocks are not a problem (they are not) - if not any([ref.symbol.name in ('npti',) - for ref in loop.stop_expr.walk(Reference)]): - if skip_for_correctness(): + if not any(ref.symbol.name in ('npti',) + for ref in loop.stop_expr.walk(Reference)): + if skip_for_correctness(loop): continue # pnd_lev requires manual privatisation of ztmp - if any([name in routine_name for name in ('tab_', 'pnd_')]): + if any(name in routine_name for name in ('tab_', 'pnd_')): opts = {"force": True} try: diff --git a/examples/psyir/create.py b/examples/psyir/create.py index ba62ccb138..062cbea2d7 100644 --- a/examples/psyir/create.py +++ b/examples/psyir/create.py @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: R. W. Ford, STFC Daresbury Lab -# Modifications: A. R. Porter, STFC Daresbury Lab +# Modifications: A. R. Porter and S. Siso, STFC Daresbury Lab '''A simple Python script showing how to create a PSyIR tree using the create methods. In order to use it you must first install @@ -47,7 +47,7 @@ ''' from psyclone.psyir.nodes import Reference, Literal, UnaryOperation, \ - BinaryOperation, NaryOperation, Assignment, IfBlock, Loop, \ + BinaryOperation, Assignment, IfBlock, Loop, IntrinsicCall, \ Container, ArrayReference, Call, Routine, FileContainer from psyclone.psyir.symbols import DataSymbol, RoutineSymbol, SymbolTable, \ ContainerSymbol, ArgumentInterface, ScalarType, ArrayType, \ @@ -112,21 +112,21 @@ def tmp2(): return Reference(tmp_symbol) # Unary Operation - oper = UnaryOperation.Operator.SIN + oper = UnaryOperation.Operator.MINUS unaryoperation = UnaryOperation.create(oper, tmp2()) # Binary Operation oper = BinaryOperation.Operator.ADD binaryoperation = BinaryOperation.create(oper, one(), unaryoperation) - # Nary Operation - oper = NaryOperation.Operator.MAX - naryoperation = NaryOperation.create(oper, [tmp1(), tmp2(), one()]) + # Intrinsic + intr = IntrinsicCall.Intrinsic.MAX + intrinsic = IntrinsicCall.create(intr, [tmp1(), tmp2(), one()]) - # Operation with named args - oper = BinaryOperation.Operator.DOT_PRODUCT - binaryoperation_named = BinaryOperation.create( - oper, ("vector_a", tmp1()), ("vector_b", tmp2())) + # Intrinisc with named args + intr = IntrinsicCall.Intrinsic.DOT_PRODUCT + intrinsic_named = IntrinsicCall.create( + intr, [("vector_a", tmp1()), ("vector_b", tmp2())]) # The create method supports the usage of ":" instead # of a range from lower bound to upper bound: @@ -137,8 +137,8 @@ def tmp2(): assign2 = Assignment.create(tmp2(), zero()) assign3 = Assignment.create(tmp2(), binaryoperation) assign4 = Assignment.create(tmp1(), tmp2()) - assign5 = Assignment.create(tmp1(), naryoperation) - assign6 = Assignment.create(tmp2(), binaryoperation_named) + assign5 = Assignment.create(tmp1(), intrinsic) + assign6 = Assignment.create(tmp2(), intrinsic_named) assign7 = Assignment.create(tmparray, two()) # Call with named argument diff --git a/examples/psyir/create_structure_types.py b/examples/psyir/create_structure_types.py index ee0860bb2a..afe95dbf62 100644 --- a/examples/psyir/create_structure_types.py +++ b/examples/psyir/create_structure_types.py @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: A. R. Porter, STFC Daresbury Lab -# Modified: R. W. Ford, STFC Daresbury Lab +# Modified: R. W. Ford and S. Siso, STFC Daresbury Lab '''A Python script showing how to create and manipulate symbols of structure type within the PSyIR. In order to use it you must first install PSyclone. @@ -45,7 +45,7 @@ ''' from psyclone.psyir.nodes import Literal, KernelSchedule, Container, \ StructureReference, ArrayOfStructuresReference, Assignment, \ - BinaryOperation, Range + IntrinsicCall, Range from psyclone.psyir.symbols import DataSymbol, SymbolTable, StructureType, \ ContainerSymbol, ArgumentInterface, ScalarType, ArrayType, DataTypeSymbol,\ ImportInterface, INTEGER_TYPE, INTEGER4_TYPE, INTEGER8_TYPE, \ @@ -129,12 +129,12 @@ def int_one(): DX_REF = StructureReference.create(FIELD_SYMBOL, ["grid", "dx"]) # Array reference to component of derived type using a range -LBOUND = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - StructureReference.create(FIELD_SYMBOL, ["data"]), int_one()) -UBOUND = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - StructureReference.create(FIELD_SYMBOL, ["data"]), int_one()) +LBOUND = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [StructureReference.create(FIELD_SYMBOL, ["data"]), ("dim", int_one())]) +UBOUND = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [StructureReference.create(FIELD_SYMBOL, ["data"]), ("dim", int_one())]) MY_RANGE = Range.create(LBOUND, UBOUND) DATA_REF = StructureReference.create(FIELD_SYMBOL, [("data", [MY_RANGE])]) diff --git a/psyclone.pdf b/psyclone.pdf index 32ffc1c6b7..2a97bbed5f 100644 Binary files a/psyclone.pdf and b/psyclone.pdf differ diff --git a/src/psyclone/domain/common/transformations/kernel_module_inline_trans.py b/src/psyclone/domain/common/transformations/kernel_module_inline_trans.py index a4ffe6274b..3b164ff403 100644 --- a/src/psyclone/domain/common/transformations/kernel_module_inline_trans.py +++ b/src/psyclone/domain/common/transformations/kernel_module_inline_trans.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2017-2022, Science and Technology Facilities Council. +# Copyright (c) 2017-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -44,7 +44,7 @@ from psyclone.psyir.symbols import RoutineSymbol, DataSymbol, \ DataTypeSymbol, Symbol, ContainerSymbol, DefaultModuleInterface from psyclone.psyir.nodes import Container, ScopingNode, Reference, Routine, \ - Literal, CodeBlock, Call + Literal, CodeBlock, Call, IntrinsicCall class KernelModuleInlineTrans(Transformation): @@ -119,8 +119,11 @@ def validate(self, node, options=None): for var in kernel_schedule.walk((Reference, Call)): if isinstance(var, Reference): symbol = var.symbol - elif isinstance(var, Call): + elif isinstance(var, Call) and not isinstance(var, IntrinsicCall): symbol = var.routine + else: + # At this point it can only be a IntrinsicCall + continue if not symbol.is_import: try: var.scope.symbol_table.lookup( @@ -201,11 +204,6 @@ def _prepare_code_to_inline(code_to_inline): # Then decide which symbols need to be brought inside the subroutine symbols_to_bring_in = set() for symbol in all_symbols: - # TODO #1366: We still need a solution for intrinsics that - # currently are parsed into Calls/RoutineSymbols, for the - # moment here we skip the ones causing issues. - if symbol.name in ("random_number", ) and symbol.is_unresolved: - continue # Skip intrinsic symbols if symbol.is_unresolved or symbol.is_import: # This symbol is already in the symbol table, but adding it # to the 'symbols_to_bring_in' will make the next step bring diff --git a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py index 1aa226afba..68183a986f 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py @@ -49,7 +49,7 @@ from psyclone.psyir.frontend.fortran import FortranReader from psyclone.psyir.nodes import Routine, Call, Reference, Literal, \ Assignment, IfBlock, ArrayReference, Schedule, BinaryOperation, \ - StructureReference, FileContainer, CodeBlock + StructureReference, FileContainer, CodeBlock, IntrinsicCall from psyclone.psyir.symbols import ArrayType, DataSymbol, RoutineSymbol, \ ContainerSymbol, UnknownFortranType, ArgumentInterface, ImportInterface, \ INTEGER_TYPE, CHARACTER_TYPE, BOOLEAN_TYPE, ScalarType @@ -654,10 +654,10 @@ def _add_divisibility_check(node, position, check_status, global_size_expr, ''' check = BinaryOperation.create( BinaryOperation.Operator.NE, - BinaryOperation.create( - BinaryOperation.Operator.REM, - global_size_expr, - Literal(str(local_size), INTEGER_TYPE) + IntrinsicCall.create( + IntrinsicCall.Intrinsic.MOD, + [global_size_expr, + Literal(str(local_size), INTEGER_TYPE)] ), Literal("0", INTEGER_TYPE)) message = ("Global size is not a multiple of local size (" @@ -870,14 +870,15 @@ def _generate_set_args_call(kernel, scope): symbol = symtab.lookup_with_tag(arg.name + "_cl_mem") source = StructureReference.create(field, ['device_ptr']) dest = Reference(symbol) - bop = BinaryOperation.create(BinaryOperation.Operator.CAST, - source, dest) - assig = Assignment.create(dest.copy(), bop) + icall = IntrinsicCall.create(IntrinsicCall.Intrinsic.TRANSFER, + [source, dest]) + assig = Assignment.create(dest.copy(), icall) call_block.addchild(assig) arguments.append(Reference(symbol)) elif arg.argument_type == "grid_property": garg = kernel.arguments.find_grid_access() if arg.is_scalar: + # pylint: disable=protected-access arguments.append( StructureReference.create( symtab.lookup(garg.name), @@ -892,9 +893,10 @@ def _generate_set_args_call(kernel, scope): field, ['grid', device_grid_property]) symbol = symtab.lookup_with_tag(arg.name + "_cl_mem") dest = Reference(symbol) - bop = BinaryOperation.create(BinaryOperation.Operator.CAST, - source, dest) - assig = Assignment.create(dest.copy(), bop) + icall = IntrinsicCall.create( + IntrinsicCall.Intrinsic.TRANSFER, + [source, dest]) + assig = Assignment.create(dest.copy(), icall) call_block.addchild(assig) arguments.append(Reference(symbol)) diff --git a/src/psyclone/domain/lfric/__init__.py b/src/psyclone/domain/lfric/__init__.py index fc2eeb3547..931c237e97 100644 --- a/src/psyclone/domain/lfric/__init__.py +++ b/src/psyclone/domain/lfric/__init__.py @@ -79,4 +79,4 @@ 'LFRicExtractDriverCreator', 'LFRicInvoke', 'LFRicLoopBounds', - 'LFRicSymbolTable'] \ No newline at end of file + 'LFRicSymbolTable'] diff --git a/src/psyclone/domain/lfric/lfric_builtins.py b/src/psyclone/domain/lfric/lfric_builtins.py index b8c16e8110..6aa2469def 100644 --- a/src/psyclone/domain/lfric/lfric_builtins.py +++ b/src/psyclone/domain/lfric/lfric_builtins.py @@ -55,7 +55,7 @@ from psyclone.parse.utils import ParseError from psyclone.psyGen import BuiltIn from psyclone.psyir.nodes import (Assignment, BinaryOperation, Call, Reference, - StructureReference) + StructureReference, IntrinsicCall) from psyclone.psyir.symbols import ArrayType, RoutineSymbol from psyclone.utils import a_or_an @@ -2323,12 +2323,9 @@ def lower_to_language_level(self): # Create the PSyIR for the kernel: # call random_number(proxy0%data(df)) - # TODO #1366 - currently we have to create a Symbol for the intrinsic - # but *not* add it to the symbol table (since there's no import for - # it). This can be removed once we have proper support for intrinsics - # that are not operators. routine = RoutineSymbol("random_number") - call = Call.create(routine, arg_refs) + call = IntrinsicCall.create(IntrinsicCall.Intrinsic.RANDOM_NUMBER, + arg_refs) # Finally, replace this kernel node with the Assignment self.replace_with(call) return call @@ -2511,8 +2508,8 @@ def lower_to_language_level(self): # Create the PSyIR for the kernel: # proxy0%data(df) = SIGN(ascalar, proxy1%data) - rhs = BinaryOperation.create(BinaryOperation.Operator.SIGN, - scalar_args[0], arg_refs[1]) + rhs = IntrinsicCall.create(IntrinsicCall.Intrinsic.SIGN, + [scalar_args[0], arg_refs[1]]) assign = Assignment.create(arg_refs[0], rhs) # Finally, replace this kernel node with the Assignment self.replace_with(assign) @@ -2563,8 +2560,8 @@ def lower_to_language_level(self): # Create the PSyIR for the kernel: # proxy0%data(df) = MAX(ascalar, proxy1%data) - rhs = BinaryOperation.create(BinaryOperation.Operator.MAX, - scalar_args[0], arg_refs[1]) + rhs = IntrinsicCall.create(IntrinsicCall.Intrinsic.MAX, + [scalar_args[0], arg_refs[1]]) assign = Assignment.create(arg_refs[0], rhs) # Finally, replace this kernel node with the Assignment self.replace_with(assign) @@ -2610,8 +2607,8 @@ def lower_to_language_level(self): # Create the PSyIR for the kernel: # proxy0%data(df) = MAX(ascalar, proxy0%data) lhs = arg_refs[0] - rhs = BinaryOperation.create(BinaryOperation.Operator.MAX, - scalar_args[0], lhs.copy()) + rhs = IntrinsicCall.create(IntrinsicCall.Intrinsic.MAX, + [scalar_args[0], lhs.copy()]) assign = Assignment.create(lhs, rhs) # Finally, replace this kernel node with the Assignment self.replace_with(assign) @@ -2662,8 +2659,8 @@ def lower_to_language_level(self): # Create the PSyIR for the kernel: # proxy0%data(df) = MIN(ascalar, proxy1%data) - rhs = BinaryOperation.create(BinaryOperation.Operator.MIN, - scalar_args[0], arg_refs[1]) + rhs = IntrinsicCall.create(IntrinsicCall.Intrinsic.MIN, + [scalar_args[0], arg_refs[1]]) assign = Assignment.create(arg_refs[0], rhs) # Finally, replace this kernel node with the Assignment self.replace_with(assign) @@ -2709,8 +2706,8 @@ def lower_to_language_level(self): # Create the PSyIR for the kernel: # proxy0%data(df) = MIN(ascalar, proxy0%data) lhs = arg_refs[0] - rhs = BinaryOperation.create(BinaryOperation.Operator.MIN, - scalar_args[0], lhs.copy()) + rhs = IntrinsicCall.create(IntrinsicCall.Intrinsic.MIN, + [scalar_args[0], lhs.copy()]) assign = Assignment.create(lhs, rhs) # Finally, replace this kernel node with the Assignment self.replace_with(assign) diff --git a/src/psyclone/domain/lfric/lfric_loop_bounds.py b/src/psyclone/domain/lfric/lfric_loop_bounds.py index f7708820e7..0ff59dd44a 100644 --- a/src/psyclone/domain/lfric/lfric_loop_bounds.py +++ b/src/psyclone/domain/lfric/lfric_loop_bounds.py @@ -36,19 +36,17 @@ # Modified J. Henrichs, Bureau of Meteorology # Modified A. B. G. Chalk and N. Nobre, STFC Daresbury Lab -''' This module provides the LFRicLoopBounds Class that handles all variables +''' This module provides the LFRicLoopBounds Class that handles all variables required for specifying loop limits within an LFRic PSy-layer routine.''' -# Imports from psyclone.configuration import Config from psyclone.domain.lfric import LFRicCollection from psyclone.f2pygen import AssignGen, CommentGen, DeclGen class LFRicLoopBounds(LFRicCollection): - - ''' - Handles all variables required for specifying loop limits within + ''' + Handles all variables required for specifying loop limits within an LFRic PSy-layer routine. ''' diff --git a/src/psyclone/domain/nemo/transformations/create_nemo_kernel_trans.py b/src/psyclone/domain/nemo/transformations/create_nemo_kernel_trans.py index 3acadfd883..9d8a51879e 100644 --- a/src/psyclone/domain/nemo/transformations/create_nemo_kernel_trans.py +++ b/src/psyclone/domain/nemo/transformations/create_nemo_kernel_trans.py @@ -41,7 +41,8 @@ from psyclone.errors import LazyString from psyclone.nemo import NemoKern -from psyclone.psyir.nodes import Schedule, Loop, Call, CodeBlock, Assignment +from psyclone.psyir.nodes import (Schedule, Loop, Call, CodeBlock, Assignment, + IntrinsicCall) from psyclone.transformations import Transformation, TransformationError @@ -114,7 +115,7 @@ def validate(self, node, options=None): is not within a loop or cannot be represented as a Kernel. ''' - super(CreateNemoKernelTrans, self).validate(node, options=options) + super().validate(node, options=options) if not isinstance(node, Schedule): raise TransformationError( @@ -137,9 +138,11 @@ def validate(self, node, options=None): nodes = node.walk((Assignment, CodeBlock, Loop, Call, NemoKern), stop_type=(CodeBlock, Loop, Call, NemoKern)) if nodes and isinstance(nodes[-1], (CodeBlock, Loop, Call, NemoKern)): - raise TransformationError( - f"Error in NemoKernelTrans transformation. A NEMO Kernel " - f"cannot contain a node of type: '{type(nodes[-1]).__name__}'") + if not isinstance(nodes[-1], IntrinsicCall): + raise TransformationError( + f"Error in NemoKernelTrans transformation. A NEMO Kernel " + f"cannot contain a node of type: " + f"'{type(nodes[-1]).__name__}'") # Check for array assignments assigns = [assign for assign in nodes if @@ -153,9 +156,9 @@ def validate(self, node, options=None): raise TransformationError(LazyString( lambda: "A NEMO Kernel cannot contain array assignments but " f"found: " - f"{[node.debug_string().rstrip(chr(10)) for node in nodes]}")) + f"{[ass.debug_string().rstrip(chr(10)) for ass in assigns]}")) - def apply(self, sched, options=None): + def apply(self, node, options=None): ''' Takes a generic PSyIR Schedule and replaces it with a NEMO Kernel. @@ -168,10 +171,10 @@ def apply(self, sched, options=None): :type options: Optional[Dict[str, Any]] ''' - self.validate(sched, options=options) + self.validate(node, options=options) - nemokern = NemoKern(sched.pop_all_children(), parent=sched) - sched.addchild(nemokern) + nemokern = NemoKern(node.pop_all_children(), parent=node) + node.addchild(nemokern) # For AutoAPI documentation generation diff --git a/src/psyclone/domain/nemo/transformations/nemo_arrayrange2loop_trans.py b/src/psyclone/domain/nemo/transformations/nemo_arrayrange2loop_trans.py index 4871335b57..bdf291a343 100644 --- a/src/psyclone/domain/nemo/transformations/nemo_arrayrange2loop_trans.py +++ b/src/psyclone/domain/nemo/transformations/nemo_arrayrange2loop_trans.py @@ -47,7 +47,7 @@ from psyclone.nemo import NemoLoop from psyclone.psyGen import Transformation from psyclone.psyir.nodes import Range, Reference, ArrayReference, Call, \ - Assignment, Operation, CodeBlock, ArrayMember, Routine, BinaryOperation, \ + Assignment, CodeBlock, ArrayMember, Routine, IntrinsicCall, \ StructureReference, StructureMember, Node from psyclone.psyir.nodes.array_mixin import ArrayMixin from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE, ScalarType @@ -239,17 +239,17 @@ def validate(self, node, options=None): # Does the rhs of the assignment have any operations/calls that are not # elemental? - for cnode in assignment.rhs.walk((Operation, Call)): + for cnode in assignment.rhs.walk(Call): # Allow non elemental UBOUND and LBOUND. # TODO #2156 - add support for marking routines as being 'inquiry' # to improve this special-casing. - if isinstance(cnode, Operation): - if cnode.operator is BinaryOperation.Operator.LBOUND: + if isinstance(cnode, IntrinsicCall): + if cnode.intrinsic is IntrinsicCall.Intrinsic.LBOUND: continue - if cnode.operator is BinaryOperation.Operator.UBOUND: + if cnode.intrinsic is IntrinsicCall.Intrinsic.UBOUND: continue - name = cnode.operator.name - type_txt = "Operation" + name = cnode.intrinsic.name + type_txt = "IntrinsicCall" else: name = cnode.routine.name type_txt = "Call" @@ -276,13 +276,13 @@ def validate(self, node, options=None): references = [n for n in nodes_to_check if isinstance(n, Reference)] for reference in references: # As special case we always allow references to whole arrays as - # part of the LBOUND and UBOUND operations, regardless of the + # part of the LBOUND and UBOUND intrinsics, regardless of the # restrictions below (e.g. is a DeferredType reference). - if isinstance(reference.parent, Operation): - operator = reference.parent.operator - if operator is BinaryOperation.Operator.LBOUND: + if isinstance(reference.parent, IntrinsicCall): + intrinsic = reference.parent.intrinsic + if intrinsic is IntrinsicCall.Intrinsic.LBOUND: continue - if operator is BinaryOperation.Operator.UBOUND: + if intrinsic is IntrinsicCall.Intrinsic.UBOUND: continue # We allow any references that are part of a structure syntax - we diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 9d4a4c89b1..d80c576dfc 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -69,7 +69,7 @@ from psyclone.psyir.frontend.fparser2 import Fparser2Reader from psyclone.psyir.frontend.fortran import FortranReader from psyclone.psyir.nodes import Literal, Schedule, KernelSchedule, \ - StructureReference, BinaryOperation, Reference, Call, Assignment, \ + StructureReference, IntrinsicCall, Reference, Call, Assignment, \ ACCEnterDataDirective, ACCParallelDirective, \ ACCKernelsDirective, Container, ACCUpdateDirective, Routine from psyclone.psyir.symbols import ScalarType, INTEGER_TYPE, \ @@ -894,7 +894,7 @@ def upper_bound(self): # Bounds are independent of the grid-offset convention in use # We look-up the upper bounds by enquiring about the SIZE of # the array itself - stop = BinaryOperation(BinaryOperation.Operator.SIZE) + stop = IntrinsicCall(IntrinsicCall.Intrinsic.SIZE) # Use the data property to access the member of the field that # contains the actual grid points. api_config = Config.get().api_conf("gocean1.0") diff --git a/src/psyclone/psyad/adjoint_visitor.py b/src/psyclone/psyad/adjoint_visitor.py index 8453d4fee7..7bca1fc603 100644 --- a/src/psyclone/psyad/adjoint_visitor.py +++ b/src/psyclone/psyad/adjoint_visitor.py @@ -47,7 +47,7 @@ from psyclone.psyir.backend.visitor import PSyIRVisitor, VisitorError from psyclone.psyir.nodes import (Routine, Schedule, Reference, Node, Literal, CodeBlock, BinaryOperation, Assignment, - IfBlock) + IfBlock, IntrinsicCall) from psyclone.psyir.symbols import ArgumentInterface from psyclone.psyir.tools import DependencyTools @@ -292,11 +292,11 @@ def loop_node(self, node): for ref in expr.walk(Reference): if ref.symbol in self._active_variables: # Ignore LBOUND and UBOUND - if not (isinstance(ref.parent, BinaryOperation) and + if not (isinstance(ref.parent, IntrinsicCall) and ref.position == 0 and - ref.parent.operator in [ - BinaryOperation.Operator.LBOUND, - BinaryOperation.Operator.UBOUND]): + ref.parent.intrinsic in [ + IntrinsicCall.Intrinsic.LBOUND, + IntrinsicCall.Intrinsic.UBOUND]): raise VisitorError( f"The {description} of a loop should not contain " f"active variables, but found '{ref.name}' in " diff --git a/src/psyclone/psyad/domain/common/adjoint_utils.py b/src/psyclone/psyad/domain/common/adjoint_utils.py index 84b1bc4d11..600e0d15cf 100644 --- a/src/psyclone/psyad/domain/common/adjoint_utils.py +++ b/src/psyclone/psyad/domain/common/adjoint_utils.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2022, Science and Technology Facilities Council. +# Copyright (c) 2022-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -31,14 +31,14 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -# Authors R. W. Ford and A. R. Porter, STFC Daresbury Lab +# Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab ''' Provides various utilities in support of the PSyAD adjoint functionality. ''' from psyclone.errors import InternalError from psyclone.psyir.frontend.fortran import FortranReader -from psyclone.psyir.nodes import (UnaryOperation, BinaryOperation, +from psyclone.psyir.nodes import (IntrinsicCall, BinaryOperation, Reference, Assignment, IfBlock, Container, FileContainer, Node) from psyclone.psyir.symbols import DataSymbol @@ -102,7 +102,6 @@ def create_real_comparison(sym_table, kernel, var1, var2): datatype=var1.datatype, is_constant=True, initial_value=INNER_PRODUCT_TOLERANCE) - # TODO #1161 - the PSyIR does not support `SPACING` assign = freader.psyir_from_statement( f"MachineTol = SPACING ( MAX( ABS({var1.name}), ABS({var2.name}) ) )", sym_table) @@ -112,7 +111,7 @@ def create_real_comparison(sym_table, kernel, var1, var2): "precision of the active variables") sub_op = BinaryOperation.create(BinaryOperation.Operator.SUB, Reference(var1), Reference(var2)) - abs_op = UnaryOperation.create(UnaryOperation.Operator.ABS, sub_op) + abs_op = IntrinsicCall.create(IntrinsicCall.Intrinsic.ABS, [sub_op]) div_op = BinaryOperation.create(BinaryOperation.Operator.DIV, abs_op, Reference(mtol)) statements.append(Assignment.create(Reference(rel_diff), div_op)) diff --git a/src/psyclone/psyad/tl2ad.py b/src/psyclone/psyad/tl2ad.py index d51a891f97..5e433bf666 100644 --- a/src/psyclone/psyad/tl2ad.py +++ b/src/psyclone/psyad/tl2ad.py @@ -669,20 +669,20 @@ def _create_array_inner_product(result, array1, array2, table): # Generate a Range object for each dimension of each array for idx in range(len(array1.datatype.shape)): idx_literal = Literal(str(idx+1), INTEGER_TYPE) - lbound1 = BinaryOperation.create(BinaryOperation.Operator.LBOUND, - Reference(array1), - idx_literal.copy()) - ubound1 = BinaryOperation.create(BinaryOperation.Operator.UBOUND, - Reference(array1), - idx_literal.copy()) + lbound1 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(array1), ("dim", idx_literal.copy())]) + ubound1 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(array1), ("dim", idx_literal.copy())]) ranges1.append(Range.create(lbound1, ubound1)) - lbound2 = BinaryOperation.create(BinaryOperation.Operator.LBOUND, - Reference(array2), - idx_literal.copy()) - ubound2 = BinaryOperation.create(BinaryOperation.Operator.UBOUND, - Reference(array2), - idx_literal.copy()) + lbound2 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(array2), ("dim", idx_literal.copy())]) + ubound2 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(array2), ("dim", idx_literal.copy())]) ranges2.append(Range.create(lbound2, ubound2)) # Use these Ranges to create references for all elements of both arrays diff --git a/src/psyclone/psyad/transformations/assignment_trans.py b/src/psyclone/psyad/transformations/assignment_trans.py index 7e91fddd7b..ad61d5bed7 100644 --- a/src/psyclone/psyad/transformations/assignment_trans.py +++ b/src/psyclone/psyad/transformations/assignment_trans.py @@ -41,7 +41,7 @@ from psyclone.core import SymbolicMaths from psyclone.psyir.nodes import BinaryOperation, Assignment, Reference, \ - Literal, UnaryOperation + Literal, UnaryOperation, IntrinsicCall from psyclone.psyir.nodes.array_mixin import ArrayMixin from psyclone.psyir.symbols import REAL_TYPE from psyclone.psyir.transformations import TransformationError @@ -286,12 +286,12 @@ def validate(self, node, options=None): # arguments to the L/UBOUND intrinsics (as they will be when # array notation is used). active_vars = [] - lu_bound_ops = [BinaryOperation.Operator.LBOUND, - BinaryOperation.Operator.UBOUND] + lu_bound_ops = [IntrinsicCall.Intrinsic.LBOUND, + IntrinsicCall.Intrinsic.UBOUND] for ref in rhs_term.walk(Reference): if (ref.symbol in self._active_variables and - not (isinstance(ref.parent, BinaryOperation) and - ref.parent.operator in lu_bound_ops)): + not (isinstance(ref.parent, IntrinsicCall) and + ref.parent.intrinsic in lu_bound_ops)): active_vars.append(ref) if not active_vars: diff --git a/src/psyclone/psyad/transformations/preprocess.py b/src/psyclone/psyad/transformations/preprocess.py index a4b48da5c2..17b8ad0b46 100644 --- a/src/psyclone/psyad/transformations/preprocess.py +++ b/src/psyclone/psyad/transformations/preprocess.py @@ -33,6 +33,7 @@ # ----------------------------------------------------------------------------- # Authors: R. W. Ford and A. R. Porter, STFC Daresbury Lab # Modified: J. Henrichs, Bureau of Meteorology +# Modified: S. Siso, STFC Daresbury Lab '''Module containing a PSyAD kernel transformation script that applies any required transformations to the tangent linear PSyIR before it is @@ -41,7 +42,7 @@ ''' from psyclone.core import SymbolicMaths from psyclone.psyad.utils import node_is_passive -from psyclone.psyir.nodes import (Assignment, BinaryOperation, Reference) +from psyclone.psyir.nodes import (Assignment, IntrinsicCall, Reference) from psyclone.psyir.transformations import (DotProduct2CodeTrans, Matmul2CodeTrans, ArrayRange2LoopTrans, @@ -89,13 +90,13 @@ def preprocess_trans(kernel_psyir, active_variable_names): except TransformationError: break - for oper in kernel_psyir.walk(BinaryOperation): - if oper.operator == BinaryOperation.Operator.DOT_PRODUCT: + for call in kernel_psyir.walk(IntrinsicCall): + if call.intrinsic == IntrinsicCall.Intrinsic.DOT_PRODUCT: # Apply DOT_PRODUCT transformation - dot_product_trans.apply(oper) - elif oper.operator == BinaryOperation.Operator.MATMUL: + dot_product_trans.apply(call) + elif call.intrinsic == IntrinsicCall.Intrinsic.MATMUL: # Apply MATMUL transformation - matmul_trans.apply(oper) + matmul_trans.apply(call) # Deal with any associativity issues here as AssignmentTrans # is not able to. diff --git a/src/psyclone/psyir/backend/c.py b/src/psyclone/psyir/backend/c.py index b226017cca..e71c3a3124 100644 --- a/src/psyclone/psyir/backend/c.py +++ b/src/psyclone/psyir/backend/c.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2019-2022, Science and Technology Facilities Council +# Copyright (c) 2019-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -44,7 +44,7 @@ ''' from psyclone.psyir.backend.language_writer import LanguageWriter from psyclone.psyir.backend.visitor import VisitorError -from psyclone.psyir.nodes import BinaryOperation, UnaryOperation +from psyclone.psyir.nodes import BinaryOperation, UnaryOperation, IntrinsicCall from psyclone.psyir.symbols import ScalarType @@ -76,10 +76,10 @@ class CWriter(LanguageWriter): def __init__(self, skip_nodes=False, indent_string=" ", initial_indent_depth=0, check_global_constraints=True): - super(CWriter, self).__init__(("[", "]"), ".", skip_nodes, - indent_string, - initial_indent_depth, - check_global_constraints) + super().__init__(("[", "]"), ".", skip_nodes, + indent_string, + initial_indent_depth, + check_global_constraints) def gen_indices(self, indices, var_name=None): '''Given a list of PSyIR nodes representing the dimensions of an @@ -271,41 +271,12 @@ def operator_format(operator_str, expr_str): ''' return "(" + operator_str + expr_str + ")" - def function_format(function_str, expr_str): - ''' - :param str function_str: Name of the function. - :param str expr_str: String representation of the operand. - - :returns: C language unary function expression. - :rtype: str - ''' - return function_str + "(" + expr_str + ")" - - def cast_format(type_str, expr_str): - ''' - :param str type_str: Name of the new type. - :param str expr_str: String representation of the operand. - - :returns: C language unary casting expression. - :rtype: str - ''' - return "(" + type_str + ")" + expr_str - # Define a map with the operator string and the formatter function # associated with each UnaryOperation.Operator opmap = { UnaryOperation.Operator.MINUS: ("-", operator_format), UnaryOperation.Operator.PLUS: ("+", operator_format), UnaryOperation.Operator.NOT: ("!", operator_format), - UnaryOperation.Operator.SIN: ("sin", function_format), - UnaryOperation.Operator.COS: ("cos", function_format), - UnaryOperation.Operator.TAN: ("tan", function_format), - UnaryOperation.Operator.ASIN: ("asin", function_format), - UnaryOperation.Operator.ACOS: ("acos", function_format), - UnaryOperation.Operator.ATAN: ("atan", function_format), - UnaryOperation.Operator.ABS: ("abs", function_format), - UnaryOperation.Operator.REAL: ("float", cast_format), - UnaryOperation.Operator.SQRT: ("sqrt", function_format), } # If the instance operator exists in the map, use its associated @@ -369,7 +340,6 @@ def function_format(function_str, expr1, expr2): BinaryOperation.Operator.SUB: ("-", operator_format), BinaryOperation.Operator.MUL: ("*", operator_format), BinaryOperation.Operator.DIV: ("/", operator_format), - BinaryOperation.Operator.REM: ("%", operator_format), BinaryOperation.Operator.POW: ("pow", function_format), BinaryOperation.Operator.EQ: ("==", operator_format), BinaryOperation.Operator.NE: ("!=", operator_format), @@ -379,7 +349,6 @@ def function_format(function_str, expr1, expr2): BinaryOperation.Operator.GE: (">=", operator_format), BinaryOperation.Operator.AND: ("&&", operator_format), BinaryOperation.Operator.OR: ("||", operator_format), - BinaryOperation.Operator.SIGN: ("copysign", function_format), } # If the instance operator exists in the map, use its associated @@ -396,6 +365,90 @@ def function_format(function_str, expr1, expr2): self._visit(node.children[0]), self._visit(node.children[1])) + def intrinsiccall_node(self, node): + '''This method is called when an IntrinsicCall node is found in + the PSyIR tree. + + :param node: An IntrinsicCall PSyIR node. + :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` + + :returns: The C code as a string. + :rtype: str + + ''' + def binary_operator_format(operator_str, expr_str): + ''' + :param str operator_str: String representing the operator. + :param List[str] expr_str: String representation of the operands. + + :returns: C language operator expression. + :rtype: str + + :raise VisitorError: unexpected number of children. + ''' + if len(expr_str) != 2: + raise VisitorError( + f"The C Writer binary_operator formatter for IntrinsicCall" + f" only supports intrinsics with 2 children, but found " + f"'{operator_str}' with '{len(expr_str)}' children.") + return f"({expr_str[0]} {operator_str} {expr_str[1]})" + + def function_format(function_str, expr_str): + ''' + :param str function_str: Name of the function. + :param List[str] expr_str: String representation of the operands. + + :returns: C language unary function expression. + :rtype: str + ''' + return function_str + "(" + ", ".join(expr_str) + ")" + + def cast_format(type_str, expr_str): + ''' + :param str type_str: Name of the new type. + :param List[str] expr_str: String representation of the operands. + + :returns: C language unary casting expression. + :rtype: str + + :raise VisitorError: unexpected number of children. + ''' + if len(expr_str) != 1: + raise VisitorError( + f"The C Writer IntrinsicCall cast-style formatter " + f"only supports intrinsics with 1 child, but found " + f"'{type_str}' with '{len(expr_str)}' children.") + return "(" + type_str + ")" + expr_str[0] + + # Define a map with the intrinsic string and the formatter function + # associated with each Intrinsic + intrinsic_map = { + IntrinsicCall.Intrinsic.MOD: ("%", binary_operator_format), + IntrinsicCall.Intrinsic.SIGN: ("copysign", function_format), + IntrinsicCall.Intrinsic.SIN: ("sin", function_format), + IntrinsicCall.Intrinsic.COS: ("cos", function_format), + IntrinsicCall.Intrinsic.TAN: ("tan", function_format), + IntrinsicCall.Intrinsic.ASIN: ("asin", function_format), + IntrinsicCall.Intrinsic.ACOS: ("acos", function_format), + IntrinsicCall.Intrinsic.ATAN: ("atan", function_format), + IntrinsicCall.Intrinsic.ABS: ("abs", function_format), + IntrinsicCall.Intrinsic.REAL: ("float", cast_format), + IntrinsicCall.Intrinsic.INT: ("int", cast_format), + IntrinsicCall.Intrinsic.SQRT: ("sqrt", function_format), + } + + # If the intrinsic exists in the map, use its associated + # operator and formatter to generate the code, otherwise raise + # an Error. + try: + opstring, formatter = intrinsic_map[node.intrinsic] + except KeyError as err: + raise VisitorError( + f"The C backend does not support the '{node.intrinsic.name}' " + f"intrinsic.") from err + + return formatter(opstring, [self._visit(ch) for ch in node.children]) + def return_node(self, _): '''This method is called when a Return instance is found in the PSyIR tree. diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index 0863cb2eee..3165e4abef 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -39,27 +39,20 @@ from a PSyIR tree. ''' # pylint: disable=too-many-lines -from fparser.two import Fortran2003 - from psyclone.core import Signature from psyclone.errors import GenerationError, InternalError from psyclone.psyir.backend.language_writer import LanguageWriter from psyclone.psyir.backend.visitor import VisitorError -from psyclone.psyir.frontend.fparser2 import Fparser2Reader, \ - TYPE_MAP_FROM_FORTRAN -from psyclone.psyir.nodes import BinaryOperation, Call, CodeBlock, DataNode, \ - IntrinsicCall, Literal, Operation, Range, Routine, Schedule, \ - UnaryOperation +from psyclone.psyir.frontend.fparser2 import ( + Fparser2Reader, TYPE_MAP_FROM_FORTRAN) +from psyclone.psyir.nodes import ( + BinaryOperation, Call, CodeBlock, DataNode, IntrinsicCall, Literal, + Operation, Range, Routine, Schedule, UnaryOperation) from psyclone.psyir.symbols import ( ArgumentInterface, ArrayType, ContainerSymbol, DataSymbol, DataTypeSymbol, DeferredType, RoutineSymbol, ScalarType, Symbol, IntrinsicSymbol, SymbolTable, UnknownFortranType, UnknownType, UnresolvedInterface) -# The list of Fortran intrinsic functions that we know about (and can -# therefore distinguish from array accesses). These are taken from -# fparser. -FORTRAN_INTRINSICS = Fortran2003.Intrinsic_Name.function_names - # Mapping from PSyIR types to Fortran data types. Simply reverse the # map from the frontend, removing the special case of "double # precision", which is captured as a REAL intrinsic in the PSyIR. @@ -299,36 +292,6 @@ def add_accessibility_to_unknown_declaration(symbol): return "::".join([first_part]+parts[1:]) -def _validate_named_args(node): - '''Utility function that check that all named args occur after all - positional args. The check is applicable to Call and Operation - nodes. This is a Fortran restriction, not a PSyIR restriction. - - :param node: the node to check. - :type node: :py:class:`psyclone.psyir.nodes.Call` or subclass of \ - :py:class:`psyclone.psyir.nodes.Operation` - - raises TypeError: if the node is not a Call or Operation. - raises VisitorError: if the all of the positional arguments are \ - not before all of the named arguments. - - ''' - if not isinstance(node, (Call, Operation)): - raise TypeError( - f"The _validate_named_args utility function expects either a " - f"Call or Operation node, but found '{type(node).__name__}'.") - - found_named_arg = False - for name in node.argument_names: - if found_named_arg and not name: - raise VisitorError( - f"Fortran expects all named arguments to occur after all " - f"positional arguments but this is not the case for " - f"{str(node)}") - if name: - found_named_arg = True - - class FortranWriter(LanguageWriter): # pylint: disable=too-many-public-methods '''Implements a PSyIR-to-Fortran back end for PSyIR kernel code (not @@ -364,8 +327,6 @@ def __init__(self, skip_nodes=False, indent_string=" ", Fparser2Reader.unary_operators) self._reverse_map(self._operator_2_str, Fparser2Reader.binary_operators) - self._reverse_map(self._operator_2_str, - Fparser2Reader.nary_operators) # Create and store a DependencyTools instance for use when ordering # parameter declarations. Have to import it here as DependencyTools @@ -401,19 +362,6 @@ def _reverse_map(reverse_dict, op_map): if mapping_key not in reverse_dict: reverse_dict[mapping_key] = mapping_value.upper() - def is_intrinsic(self, operator): - '''Determine whether the supplied operator is an intrinsic - Fortran function or not. - - :param str fortran_operator: the supplied Fortran operator. - - :returns: True if the supplied Fortran operator is a Fortran \ - intrinsic and False otherwise. - :rtype: bool - - ''' - return operator in FORTRAN_INTRINSICS - def get_operator(self, operator): '''Determine the Fortran operator that is equivalent to the provided PSyIR operator. This is achieved by reversing the Fparser2Reader @@ -1234,73 +1182,33 @@ def binaryoperation_node(self, node): :rtype: str ''' - _validate_named_args(node) - lhs = self._visit(node.children[0]) rhs = self._visit(node.children[1]) - if node.argument_names[0]: - lhs = f"{node.argument_names[0]}={lhs}" - if node.argument_names[1]: - rhs = f"{node.argument_names[1]}={rhs}" try: fort_oper = self.get_operator(node.operator) - if self.is_intrinsic(fort_oper): - # This is a binary intrinsic function. - return f"{fort_oper}({lhs}, {rhs})" parent = node.parent if isinstance(parent, Operation): # We may need to enforce precedence parent_fort_oper = self.get_operator(parent.operator) - if not self.is_intrinsic(parent_fort_oper): + if precedence(fort_oper) < precedence(parent_fort_oper): + # We need brackets to enforce precedence + return f"({lhs} {fort_oper} {rhs})" + if precedence(fort_oper) == precedence(parent_fort_oper): # We still may need to enforce precedence - if precedence(fort_oper) < precedence(parent_fort_oper): + if (isinstance(parent, UnaryOperation) or + (isinstance(parent, BinaryOperation) and + parent.children[1] == node)): # We need brackets to enforce precedence + # as a) a unary operator is performed + # before a binary operator and b) floating + # point operations are not actually + # associative due to rounding errors. return f"({lhs} {fort_oper} {rhs})" - if precedence(fort_oper) == precedence(parent_fort_oper): - # We still may need to enforce precedence - if (isinstance(parent, UnaryOperation) or - (isinstance(parent, BinaryOperation) and - parent.children[1] == node)): - # We need brackets to enforce precedence - # as a) a unary operator is performed - # before a binary operator and b) floating - # point operations are not actually - # associative due to rounding errors. - return f"({lhs} {fort_oper} {rhs})" return f"{lhs} {fort_oper} {rhs}" except KeyError as error: raise VisitorError( f"Unexpected binary op '{node.operator}'.") from error - def naryoperation_node(self, node): - '''This method is called when an NaryOperation instance is found in - the PSyIR tree. - - :param node: an NaryOperation PSyIR node. - :type node: :py:class:`psyclone.psyir.nodes.NaryOperation` - - :returns: the Fortran code as a string. - :rtype: str - - :raises VisitorError: if an unexpected N-ary operator is found. - - ''' - _validate_named_args(node) - - arg_list = [] - for idx, child in enumerate(node.children): - if node.argument_names[idx]: - arg_list.append( - f"{node.argument_names[idx]}={self._visit(child)}") - else: - arg_list.append(self._visit(child)) - try: - fort_oper = self.get_operator(node.operator) - return f"{fort_oper}(" + ", ".join(arg_list) + ")" - except KeyError as error: - raise VisitorError( - f"Unexpected N-ary op '{node.operator}'") from error - def range_node(self, node): '''This method is called when a Range instance is found in the PSyIR tree. @@ -1517,26 +1425,14 @@ def unaryoperation_node(self, node): content = self._visit(node.children[0]) try: fort_oper = self.get_operator(node.operator) - if self.is_intrinsic(fort_oper): - # This is a unary intrinsic function. - if node.argument_names[0]: - result = f"{fort_oper}({node.argument_names[0]}={content})" - else: - result = f"{fort_oper}({content})" - return result - # It's not an intrinsic function so we need to consider the - # parent node. If that is a UnaryOperation or a BinaryOperation + # If the parent node is a UnaryOperation or a BinaryOperation # such as '-' or '**' then we need parentheses. This ensures we # don't generate invalid Fortran such as 'a ** -b' or 'a - -b'. parent = node.parent if isinstance(parent, UnaryOperation): - parent_fort_oper = self.get_operator(parent.operator) - if not self.is_intrinsic(parent_fort_oper): - return f"({fort_oper}{content})" + return f"({fort_oper}{content})" if isinstance(parent, BinaryOperation): - parent_fort_oper = self.get_operator(parent.operator) - if (not self.is_intrinsic(parent_fort_oper) and - node is parent.children[1]): + if node is parent.children[1]: return f"({fort_oper}{content})" return f"{fort_oper}{content}" @@ -1690,18 +1586,38 @@ def standalonedirective_node(self, node): return result - def call_node(self, node): - '''Translate the PSyIR call node to Fortran. + def _gen_arguments(self, node): + '''Utility function that check that all named args occur after all + positional args. This is a Fortran restriction, not a PSyIR + restriction. And if they are valid, it returns the whole list of + arguments. - :param node: a Call PSyIR node. + :param node: the node to check. :type node: :py:class:`psyclone.psyir.nodes.Call` - - :returns: the equivalent Fortran code. + :returns: string representation of the complete list of arguments. :rtype: str + raises TypeError: if the provided node is not a Call. + raises VisitorError: if the all of the positional arguments are + not before all of the named arguments. + ''' - _validate_named_args(node) + if not isinstance(node, Call): + raise TypeError( + f"The _gen_arguments utility function expects a " + f"Call node, but found '{type(node).__name__}'.") + + found_named_arg = False + for name in node.argument_names: + if found_named_arg and not name: + raise VisitorError( + f"Fortran expects all named arguments to occur after all " + f"positional arguments but this is not the case for " + f"{str(node)}") + if name: + found_named_arg = True + # All arguments have been validated, proceed to generate them result_list = [] for idx, child in enumerate(node.children): if node.argument_names[idx]: @@ -1709,13 +1625,26 @@ def call_node(self, node): f"{node.argument_names[idx]}={self._visit(child)}") else: result_list.append(self._visit(child)) - args = ", ".join(result_list) + return ", ".join(result_list) + + def call_node(self, node): + '''Translate the PSyIR call node to Fortran. + + :param node: a Call PSyIR node. + :type node: :py:class:`psyclone.psyir.nodes.Call` + + :returns: the equivalent Fortran code. + :rtype: str + + ''' + args = self._gen_arguments(node) if isinstance(node, IntrinsicCall) and node.routine.name in [ "ALLOCATE", "DEALLOCATE"]: # An allocate/deallocate doesn't have 'call'. return f"{self._nindent}{node.routine.name}({args})\n" if not node.parent or isinstance(node.parent, Schedule): return f"{self._nindent}call {node.routine.name}({args})\n" + # Otherwise it is inside-expression function call return f"{node.routine.name}({args})" diff --git a/src/psyclone/psyir/backend/sir.py b/src/psyclone/psyir/backend/sir.py index e539fc45d3..a0d598cafe 100644 --- a/src/psyclone/psyir/backend/sir.py +++ b/src/psyclone/psyir/backend/sir.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2019-2022, Science and Technology Facilities Council +# Copyright (c) 2019-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: R. W. Ford, STFC Daresbury Lab -# Modified by: A. R. Porter and N. Nobre, STFC Daresbury Lab +# Modified by: A. R. Porter, N. Nobre and S. Siso, STFC Daresbury Lab '''SIR PSyIR backend. Generates SIR code from PSyIR nodes. Currently limited to PSyIR Kernel schedules as PSy-layer PSyIR already has a @@ -42,7 +42,7 @@ from psyclone.nemo import NemoLoop, NemoKern from psyclone.psyir.backend.visitor import PSyIRVisitor, VisitorError from psyclone.psyir.nodes import ArrayReference, BinaryOperation, Literal, \ - Reference, UnaryOperation, NaryOperation + Reference, UnaryOperation, IntrinsicCall from psyclone.psyir.symbols import ScalarType # Mapping from PSyIR data types to SIR types. @@ -314,13 +314,6 @@ def binaryoperation_node(self, node): operator to SIR. ''' - # Specify any PSyIR intrinsic operators as these are typically - # mapped to SIR functions and are therefore treated - # differently to other operators. - intrinsic_operators = [ - BinaryOperation.Operator.MIN, BinaryOperation.Operator.MAX, - BinaryOperation.Operator.SIGN] - # Specify the mapping of PSyIR binary operators to SIR binary # operators. binary_operators = { @@ -336,10 +329,7 @@ def binaryoperation_node(self, node): BinaryOperation.Operator.GE: '>=', BinaryOperation.Operator.GT: '>', BinaryOperation.Operator.AND: '&&', - BinaryOperation.Operator.OR: '||', - BinaryOperation.Operator.MIN: 'math::min', - BinaryOperation.Operator.MAX: 'math::max', - BinaryOperation.Operator.SIGN: 'math::sign'} + BinaryOperation.Operator.OR: '||'} self._depth += 1 lhs = self._visit(node.children[0]) @@ -352,28 +342,11 @@ def binaryoperation_node(self, node): rhs = self._visit(node.children[1]) self._depth -= 1 - if node.operator in intrinsic_operators: - if node.operator is BinaryOperation.Operator.SIGN: - # This is a special case as the implementation of SIGN - # in the PSyIR (which uses the Fortran implementation) - # is different to that in SIR (which uses the C - # implementation). - # [F] SIGN(A,B) == [C] FABS(A)*SIGN(B) - c_abs_fun = (f"make_fun_call_expr(\"math::fabs\", " - f"[{lhs.strip()}])") - c_sign_fun = (f"make_fun_call_expr(\"math::sign\", " - f"[{rhs.strip()}])") - result = (f"make_binary_operator({c_abs_fun}, " - f"\"*\", {c_sign_fun})") - else: - result = (f"{self._nindent}{self._indent}make_fun_call_expr(" - f"\"{oper}\", [{lhs.strip()}], [{rhs.strip()}])") - else: - result = f"{self._nindent}make_binary_operator(\n{lhs}" - # For better formatting, remove the newline if one exists. - result = result.rstrip("\n") + ",\n" - result += f"{self._nindent}{self._indent}\"{oper}\",\n{rhs}\n"\ - f"{self._nindent}{self._indent})\n" + result = f"{self._nindent}make_binary_operator(\n{lhs}" + # For better formatting, remove the newline if one exists. + result = result.rstrip("\n") + ",\n" + result += f"{self._nindent}{self._indent}\"{oper}\",\n{rhs}\n"\ + f"{self._nindent}{self._indent})\n" return result def reference_node(self, node): @@ -461,11 +434,9 @@ def unaryoperation_node(self, node): ''' # Currently only '-' and intrinsics are supported in the SIR mapping. - intrinsic_operators = [UnaryOperation.Operator.ABS] - unary_operators = { UnaryOperation.Operator.MINUS: '-', - UnaryOperation.Operator.ABS: 'math::fabs'} + } try: oper = unary_operators[node.operator] except KeyError as err: @@ -473,12 +444,6 @@ def unaryoperation_node(self, node): f"Method unaryoperation_node in class SIRWriter, unsupported " f"operator '{node.operator}' found.") from err - if node.operator in intrinsic_operators: - rhs = self._visit(node.children[0]) - result = (f"{self._nindent}{self._indent}make_fun_call_expr(" - f"\"{oper}\", [{rhs.strip()}])") - return result - if isinstance(node.children[0], Literal): # The unary minus operator is being applied to a # literal. This is a special case as the literal value can @@ -558,12 +523,12 @@ def schedule_node(self, node): result += self._visit(child) return result - def naryoperation_node(self, node): - '''This method is called when an NaryOperation instance is found in + def intrinsiccall_node(self, node): + '''This method is called when an Intrinsic node is found in the PSyIR tree. - :param node: a NaryOperation PSyIR node. - :type node: :py:class:`psyclone.psyir.nodes.NaryOperation` + :param node: an IntrinsicCall PSyIR node. + :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` :returns: the SIR Python code. :rtype: str @@ -575,26 +540,43 @@ def naryoperation_node(self, node): # Specify the mapping of PSyIR nary operators to SIR nary # operators. The assumption here is that the nary operators # are SIR intrinsics. - nary_operators = { - NaryOperation.Operator.MIN: 'math::min', - NaryOperation.Operator.MAX: 'math::max'} + supported_intrinsics = { + IntrinsicCall.Intrinsic.ABS: 'math::fabs', + IntrinsicCall.Intrinsic.MIN: 'math::min', + IntrinsicCall.Intrinsic.MAX: 'math::max', + IntrinsicCall.Intrinsic.SIGN: 'math::sign'} try: - oper = nary_operators[node.operator] + intrinsic = supported_intrinsics[node.intrinsic] except KeyError as err: - oper_names = [operator.name for operator in nary_operators] + intr_names = [intr.name for intr in supported_intrinsics] raise VisitorError( - f"Method naryoperation_node in class SIRWriter, unsupported " - f"operator '{node.operator}' found. Expected one of " - f"'{oper_names}'.") from err + f"Method intrinsiccall_node in class SIRWriter, unsupported " + f"intrinsic '{node.intrinsic.name}' found. Expected one of " + f"'{intr_names}'.") from err - arg_list = [] self._depth += 1 - for child in node.children: - arg_list.append(f"[{self._visit(child).strip()}]") + + if node.intrinsic is IntrinsicCall.Intrinsic.SIGN: + # This is a special case as the implementation of SIGN + # in the PSyIR (which uses the Fortran implementation) + # is different to that in SIR (which uses the C + # implementation). + # [F] SIGN(A,B) == [C] FABS(A)*SIGN(B) + c_abs_fun = (f"make_fun_call_expr(\"math::fabs\", " + f"[{self._visit(node.children[0]).strip()}])") + c_sign_fun = (f"make_fun_call_expr(\"math::sign\", " + f"[{self._visit(node.children[1]).strip()}])") + result = (f"make_binary_operator({c_abs_fun}, " + f"\"*\", {c_sign_fun})") + else: + # Everything else it is a SIR intrinsics + arg_list = [] + for child in node.children: + arg_list.append(f"[{self._visit(child).strip()}]") + arg_str = ", ".join(arg_list) + result = (f"{self._nindent}{self._indent}make_fun_call_expr(" + f"\"{intrinsic}\", {arg_str})") + self._depth -= 1 - arg_str = ", ".join(arg_list) - # The assumption here is that the supported operators are intrinsics - result = (f"{self._nindent}{self._indent}make_fun_call_expr(" - f"\"{oper}\", {arg_str})") return result diff --git a/src/psyclone/psyir/backend/sympy_writer.py b/src/psyclone/psyir/backend/sympy_writer.py index faf4c6a35c..807eb0b4c7 100644 --- a/src/psyclone/psyir/backend/sympy_writer.py +++ b/src/psyclone/psyir/backend/sympy_writer.py @@ -46,8 +46,7 @@ from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.backend.visitor import VisitorError from psyclone.psyir.frontend.sympy_reader import SymPyReader -from psyclone.psyir.nodes import (BinaryOperation, DataNode, NaryOperation, - Range, Reference, UnaryOperation) +from psyclone.psyir.nodes import DataNode, Range, Reference, IntrinsicCall from psyclone.psyir.symbols import (ArrayType, ScalarType, SymbolTable) @@ -133,25 +132,22 @@ def __init__(self): # The set of intrinsic Fortran operations that need a rename or # are case sensitive in SymPy: self._intrinsic = set() - self._op_to_str = {} - - # Create the mapping of special operators/functions to the - # name SymPy expects. - for operator, op_str in [(NaryOperation.Operator.MAX, "Max"), - (BinaryOperation.Operator.MAX, "Max"), - (NaryOperation.Operator.MIN, "Min"), - (BinaryOperation.Operator.MIN, "Min"), - (UnaryOperation.Operator.FLOOR, "floor"), - (UnaryOperation.Operator.TRANSPOSE, - "transpose"), - (BinaryOperation.Operator.REM, "Mod"), - # exp is needed for a test case only, in - # general the maths functions can just be - # handled as unknown SymPy functions. - (UnaryOperation.Operator.EXP, "exp"), - ]: - self._intrinsic.add(op_str) - self._op_to_str[operator] = op_str + self._intrinsic_to_str = {} + + # Create the mapping of intrinsics to the name SymPy expects. + for intr, intr_str in [(IntrinsicCall.Intrinsic.MAX, "Max"), + (IntrinsicCall.Intrinsic.MIN, "Min"), + (IntrinsicCall.Intrinsic.FLOOR, "floor"), + (IntrinsicCall.Intrinsic.TRANSPOSE, + "transpose"), + (IntrinsicCall.Intrinsic.MOD, "Mod"), + # exp is needed for a test case only, in + # general the maths functions can just be + # handled as unknown sympy functions. + (IntrinsicCall.Intrinsic.EXP, "exp"), + ]: + self._intrinsic.add(intr_str) + self._intrinsic_to_str[intr] = intr_str # ------------------------------------------------------------------------- def __new__(cls, *expressions): @@ -547,48 +543,43 @@ def literal_node(self, node): # information can be ignored. return node.value - # ------------------------------------------------------------------------- - def get_operator(self, operator): - '''Determine the operator that is equivalent to the provided - PSyIR operator. This implementation checks for certain functions - that SymPy supports: Max, Min, Mod, etc. These functions must be - spelled with a capital first letter, otherwise SymPy will handle - them as unknown functions. If none of these special operators - are given, the base implementation is called (which will return - the Fortran syntax). - - :param operator: a PSyIR operator. - :type operator: :py:class:`psyclone.psyir.nodes.Operation.Operator` - - :returns: the operator as string. - :rtype: str + def intrinsiccall_node(self, node): + ''' This method is called when an IntrinsicCall instance is found in + the PSyIR tree. The Sympy backend will use the exact sympy name for + some math intrinsics (listed in _intrinsic_to_str) and will remove + named arguments. - :raises KeyError: if the supplied operator is not known. + :param node: an IntrinsicCall PSyIR node. + :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` - ''' + :returns: the SymPy representation for the Intrinsic. + :rtype: str + ''' + # Sympy does not support argument names, remove them for now + if any(node.argument_names): + # TODO #2302: This is not totally right without canonical intrinsic + # positions for arguments. One alternative is to refuse it with: + # raise VisitorError( + # f"Named arguments are not supported by SymPy but found: " + # f"'{node.debug_string()}'.") + # but this leaves sympy comparisons almost always giving false when + # out of order arguments are rare, so instead we ignore it for now. + + # It makes a copy (of the parent because if matters to the call + # visitor) because we don't want to delete the original arg names + parent = node.parent.copy() + node = parent.children[node.position] + for idx in range(len(node.argument_names)): + # pylint: disable=protected-access + node._argument_names[idx] = (node._argument_names[idx][0], + None) try: - return self._op_to_str[operator] + name = self._intrinsic_to_str[node.intrinsic] + args = self._gen_arguments(node) + return f"{self._nindent}{name}({args})" except KeyError: - return super().get_operator(operator) - - # ------------------------------------------------------------------------- - def is_intrinsic(self, operator): - '''Determine whether the supplied operator is an intrinsic - function (i.e. needs to be used as `f(a,b)`) or not (i.e. used - as `a + b`). This tests for known SymPy names of these functions - (e.g. Max), and otherwise calls the function in the base class. - - :param str operator: the supplied operator. - - :returns: true if the supplied operator is an - intrinsic and false otherwise. - - ''' - if operator in self._intrinsic: - return True - - return super().is_intrinsic(operator) + return super().call_node(node) # ------------------------------------------------------------------------- def reference_node(self, node): diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 191c3d3984..5287c2cf4b 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -51,7 +51,7 @@ from psyclone.psyir.nodes import ( ArrayMember, ArrayOfStructuresReference, ArrayReference, Assignment, BinaryOperation, Call, CodeBlock, Container, Directive, FileContainer, - IfBlock, IntrinsicCall, Literal, Loop, Member, NaryOperation, Node, Range, + IfBlock, IntrinsicCall, Literal, Loop, Member, Node, Range, Reference, Return, Routine, Schedule, StructureReference, UnaryOperation, WhileLoop) from psyclone.psyir.nodes.array_mixin import ArrayMixin @@ -104,7 +104,7 @@ SUPPORTED_ROUTINE_PREFIXES = ["ELEMENTAL", "PURE", "IMPURE"] -# TODO #1987. It may be that this method could be made more general so +# TODO #2302: It may be that this method could be made more general so # that it works for more intrinsics, to help minimise the number of # canonicalise_* functions. def _canonicalise_minmaxsum(arg_nodes, arg_names, node): @@ -120,11 +120,11 @@ def _canonicalise_minmaxsum(arg_nodes, arg_names, node): different forms that are allowed in Fortran. In general Fortran supports all arguments being named, all - arguments being positional and everything inbetween, as long as + arguments being positional and everything in-between, as long as all named arguments follow all positional arguments. For example, both SUM(A, DIM, MASK) and SUM(DIM=DIM, MASK=MASK, - ARRAY=A) are equivalant in Fortran. + ARRAY=A) are equivalent in Fortran. The PSyIR canonical form has all required arguments as positional arguments and all optional arguments as named arguments, which @@ -477,7 +477,7 @@ def _check_args(array, dim): f"Range type but found '{type(array.indices[dim-1]).__name__}'.") -def _is_bound_full_extent(array, dim, operator): +def _is_bound_full_extent(array, dim, intrinsic): '''A Fortran array section with a missing lower bound implies the access starts at the first element and a missing upper bound implies the access ends at the last element e.g. a(:,:) @@ -499,12 +499,12 @@ def _is_bound_full_extent(array, dim, operator): :param array: the node to check. :type array: :py:class:`pysclone.psyir.nodes.ArrayMixin` :param int dim: the dimension index to use. - :param operator: the operator to check. - :type operator: \ - :py:class:`psyclone.psyir.nodes.binaryoperation.Operator.LBOUND` \ - or :py:class:`psyclone.psyir.nodes.binaryoperation.Operator.UBOUND` + :param intrinsic: the intrinsic to check. + :type intrinsic: + :py:class:`psyclone.psyir.nodes.IntrinsicCall.Intrinsic.LBOUND` | + :py:class:`psyclone.psyir.nodes.IntrinsicCall.Intrinsic.UBOUND` - :returns: True if the supplied array has the expected properties, \ + :returns: True if the supplied array has the expected properties, otherwise returns False. :rtype: bool @@ -513,26 +513,26 @@ def _is_bound_full_extent(array, dim, operator): ''' _check_args(array, dim) - if operator == BinaryOperation.Operator.LBOUND: + if intrinsic == IntrinsicCall.Intrinsic.LBOUND: index = 0 - elif operator == BinaryOperation.Operator.UBOUND: + elif intrinsic == IntrinsicCall.Intrinsic.UBOUND: index = 1 else: raise TypeError( - f"'operator' argument expected to be LBOUND or UBOUND but " - f"found '{type(operator).__name__}'.") + f"'intrinsic' argument expected to be LBOUND or UBOUND but " + f"found '{type(intrinsic).__name__}'.") # The first element of the array (index 0) relates to the first # dimension (dim 1), so we need to reduce dim by 1. bound = array.indices[dim-1].children[index] - if not isinstance(bound, BinaryOperation): + if not isinstance(bound, IntrinsicCall): return False reference = bound.children[0] literal = bound.children[1] - if bound.operator != operator: + if bound.intrinsic != intrinsic: return False if (not isinstance(literal, Literal) or @@ -632,10 +632,10 @@ def _is_range_full_extent(my_range): dim = array.children.index(my_range) + 1 # Check lower bound is_lower = _is_bound_full_extent( - array, dim, BinaryOperation.Operator.LBOUND) + array, dim, IntrinsicCall.Intrinsic.LBOUND) # Check upper bound is_upper = _is_bound_full_extent( - array, dim, BinaryOperation.Operator.UBOUND) + array, dim, IntrinsicCall.Intrinsic.UBOUND) # Check step (index 2 is the step index for the range function) is_step = _is_array_range_literal(array, dim, 2, 1) return is_lower and is_upper and is_step @@ -1071,24 +1071,7 @@ class Fparser2Reader(): unary_operators = OrderedDict([ ('+', UnaryOperation.Operator.PLUS), ('-', UnaryOperation.Operator.MINUS), - ('.not.', UnaryOperation.Operator.NOT), - ('abs', UnaryOperation.Operator.ABS), - ('ceiling', UnaryOperation.Operator.CEIL), - ('floor', UnaryOperation.Operator.FLOOR), - ('transpose', UnaryOperation.Operator.TRANSPOSE), - ('exp', UnaryOperation.Operator.EXP), - ('log', UnaryOperation.Operator.LOG), - ('log10', UnaryOperation.Operator.LOG10), - ('sin', UnaryOperation.Operator.SIN), - ('asin', UnaryOperation.Operator.ASIN), - ('cos', UnaryOperation.Operator.COS), - ('acos', UnaryOperation.Operator.ACOS), - ('tan', UnaryOperation.Operator.TAN), - ('atan', UnaryOperation.Operator.ATAN), - ('sqrt', UnaryOperation.Operator.SQRT), - ('real', UnaryOperation.Operator.REAL), - ('nint', UnaryOperation.Operator.NINT), - ('int', UnaryOperation.Operator.INT)]) + ('.not.', UnaryOperation.Operator.NOT)]) binary_operators = OrderedDict([ ('+', BinaryOperation.Operator.ADD), @@ -1111,33 +1094,7 @@ class Fparser2Reader(): ('>', BinaryOperation.Operator.GT), ('.gt.', BinaryOperation.Operator.GT), ('.and.', BinaryOperation.Operator.AND), - ('.or.', BinaryOperation.Operator.OR), - ('dot_product', BinaryOperation.Operator.DOT_PRODUCT), - ('int', BinaryOperation.Operator.INT), - ('real', BinaryOperation.Operator.REAL), - ('sign', BinaryOperation.Operator.SIGN), - ('size', BinaryOperation.Operator.SIZE), - ('lbound', BinaryOperation.Operator.LBOUND), - ('ubound', BinaryOperation.Operator.UBOUND), - ('max', BinaryOperation.Operator.MAX), - ('min', BinaryOperation.Operator.MIN), - ('mod', BinaryOperation.Operator.REM), - ('matmul', BinaryOperation.Operator.MATMUL), - ('transfer', BinaryOperation.Operator.CAST)]) - - nary_operators = OrderedDict([ - ('max', NaryOperation.Operator.MAX), - ('min', NaryOperation.Operator.MIN)]) - - intrinsics = OrderedDict([ - ('allocate', IntrinsicCall.Intrinsic.ALLOCATE), - ('deallocate', IntrinsicCall.Intrinsic.DEALLOCATE), - ('random', IntrinsicCall.Intrinsic.RANDOM_NUMBER), - ('minval', IntrinsicCall.Intrinsic.MINVAL), - ('maxval', IntrinsicCall.Intrinsic.MAXVAL), - ('sum', IntrinsicCall.Intrinsic.SUM), - ('tiny', IntrinsicCall.Intrinsic.TINY), - ('huge', IntrinsicCall.Intrinsic.HUGE)]) + ('.or.', BinaryOperation.Operator.OR)]) def __init__(self): # Map of fparser2 node types to handlers (which are class methods) @@ -2246,6 +2203,8 @@ def _get_partial_datatype(self, node, scope, visibility_map): node.children[2].items = tuple(orig_entity_decl_list) node.children[2].children[0].items = tuple(orig_entity_decl_children) + # Return the init_expr detached from the temporal symbol + init_expr = init_expr.detach() if init_expr is not None else None return datatype, init_expr def _process_parameter_stmts(self, nodes, parent): @@ -3719,8 +3678,8 @@ def _where_construct_handler(self, node, parent): loop.addchild(Literal("1", integer_type)) # Add loop upper bound - we use the SIZE operator to query the # extent of the current array dimension - size_node = BinaryOperation(BinaryOperation.Operator.SIZE, - parent=loop) + size_node = IntrinsicCall(IntrinsicCall.Intrinsic.SIZE, + parent=loop) loop.addchild(size_node) # Create the first argument to the SIZE operator @@ -3992,22 +3951,18 @@ def _data_ref_handler(self, node, parent): def _unary_op_handler(self, node, parent): ''' - Transforms an fparser2 UnaryOpBase or Intrinsic_Function_Reference - to the PSyIR representation. + Transforms an fparser2 UnaryOpBase to its PSyIR representation. :param node: node in fparser2 AST. - :type node: :py:class:`fparser.two.utils.UnaryOpBase` or \ - :py:class:`fparser.two.Fortran2003.Intrinsic_Function_Reference` + :type node: :py:class:`fparser.two.utils.UnaryOpBase` :param parent: Parent node of the PSyIR node we are constructing. :type parent: :py:class:`psyclone.psyir.nodes.Node` :return: PSyIR representation of node :rtype: :py:class:`psyclone.psyir.nodes.UnaryOperation` - :raises NotImplementedError: if the supplied operator is not \ - supported by this handler. - :raises InternalError: if the fparser parse tree does not have the \ - expected structure. + :raises NotImplementedError: if the supplied operator is not + supported by this handler. ''' operator_str = str(node.items[0]).lower() @@ -4017,67 +3972,28 @@ def _unary_op_handler(self, node, parent): # Operator not supported, it will produce a CodeBlock instead raise NotImplementedError(operator_str) from err - if isinstance(node.items[1], Fortran2003.Actual_Arg_Spec_List): - if len(node.items[1].items) > 1: - # We have more than one argument and therefore this is not a - # unary operation! - raise InternalError( - f"Operation '{node}' has more than one argument and is " - f"therefore not unary!") - node_list = node.items[1].items - else: - node_list = [node.items[1]] unary_op = UnaryOperation(operator, parent=parent) - - # Store the names of any named args - arg_nodes, arg_names = _get_arg_names(node_list) - - self.process_nodes(parent=unary_op, nodes=arg_nodes) - - # Detach the child and add it again with the argument - # name - child = unary_op.children[0] - child.detach() - unary_op.append_named_arg(arg_names[0], child) - + self.process_nodes(parent=unary_op, nodes=[node.items[1]]) return unary_op def _binary_op_handler(self, node, parent): ''' - Transforms an fparser2 BinaryOp or Intrinsic_Function_Reference to - the PSyIR representation. + Transforms an fparser2 BinaryOp to its PSyIR representation. :param node: node in fparser2 AST. - :type node: :py:class:`fparser.two.utils.BinaryOpBase` or \ - :py:class:`fparser.two.Fortran2003.Intrinsic_Function_Reference` + :type node: :py:class:`fparser.two.utils.BinaryOpBase` :param parent: Parent node of the PSyIR node we are constructing. :type parent: :py:class:`psyclone.psyir.nodes.Node` :returns: PSyIR representation of node :rtype: :py:class:`psyclone.psyir.nodes.BinaryOperation` - :raises NotImplementedError: if the supplied operator/intrinsic is \ - not supported by this handler. - :raises InternalError: if the fparser parse tree does not have the \ - expected structure. + :raises NotImplementedError: if the supplied operator is not supported + by this handler. ''' - if isinstance(node, Fortran2003.Intrinsic_Function_Reference): - operator_str = node.items[0].string.lower() - # Arguments are held in an Actual_Arg_Spec_List - if not isinstance(node.items[1], Fortran2003.Actual_Arg_Spec_List): - raise InternalError( - f"Unexpected fparser parse tree for binary intrinsic " - f"operation '{node}'. Expected second child to be " - f"Actual_Arg_Spec_List but got '{type(node.items[1])}'.") - arg_nodes = node.items[1].items - if len(arg_nodes) != 2: - raise InternalError( - f"Binary operator should have exactly two arguments but " - f"found {len(arg_nodes)} for '{node}'.") - else: - operator_str = node.items[1].lower() - arg_nodes = [node.items[0], node.items[2]] + operator_str = node.items[1].lower() + arg_nodes = [node.items[0], node.items[2]] try: operator = Fparser2Reader.binary_operators[operator_str] @@ -4086,135 +4002,48 @@ def _binary_op_handler(self, node, parent): raise NotImplementedError(operator_str) from err binary_op = BinaryOperation(operator, parent=parent) - - # Store the names of any named args - new_arg_nodes, arg_names = _get_arg_names(arg_nodes) - - self.process_nodes(parent=binary_op, nodes=[new_arg_nodes[0]]) - self.process_nodes(parent=binary_op, nodes=[new_arg_nodes[1]]) - - # Detach the children and add them again with the argument - # names - child_list = binary_op.children[:] - for child in child_list: - child.detach() - for idx, child in enumerate(child_list): - binary_op.append_named_arg(arg_names[idx], child) - + self.process_nodes(parent=binary_op, nodes=[arg_nodes[0]]) + self.process_nodes(parent=binary_op, nodes=[arg_nodes[1]]) return binary_op - def _nary_op_handler(self, node, parent): - ''' - Transforms an fparser2 Intrinsic_Function_Reference with three or - more arguments to the PSyIR representation. - :param node: node in fparser2 Parse Tree. - :type node: \ - :py:class:`fparser.two.Fortran2003.Intrinsic_Function_Reference` - :param parent: Parent node of the PSyIR node we are constructing. - :type parent: :py:class:`psyclone.psyir.nodes.Node` - - :returns: PSyIR representation of node. - :rtype: :py:class:`psyclone.psyir.nodes.NaryOperation` - - :raises NotImplementedError: if the supplied Intrinsic is not \ - supported by this handler. - :raises InternalError: if the fparser parse tree does not have the \ - expected structure. - - ''' - operator_str = str(node.items[0]).lower() - try: - operator = Fparser2Reader.nary_operators[operator_str] - except KeyError as err: - # Intrinsic not supported, it will produce a CodeBlock instead - raise NotImplementedError(operator_str) from err - - nary_op = NaryOperation(operator, parent=parent) - - if not isinstance(node.items[1], Fortran2003.Actual_Arg_Spec_List): - raise InternalError( - f"Expected second 'item' of N-ary intrinsic '{node}' in " - f"fparser parse tree to be an Actual_Arg_Spec_List but found " - f"'{type(node.items[1])}'.") - if len(node.items[1].items) < 3: - raise InternalError( - f"An N-ary operation must have more than two arguments but " - f"found {len(node.items[1].items)} for '{node}'.") - - # node.items[1] is a Fortran2003.Actual_Arg_Spec_List so we have - # to process the `items` of that... - - # Store the names of any named args - arg_nodes, arg_names = _get_arg_names(node.items[1].items) - - self.process_nodes(parent=nary_op, nodes=arg_nodes) - - # Detach the children and add them again with the argument - # names - child_list = nary_op.children[:] - for child in child_list: - child.detach() - for idx, child in enumerate(child_list): - nary_op.append_named_arg(arg_names[idx], child) - - return nary_op - def _intrinsic_handler(self, node, parent): '''Transforms an fparser2 Intrinsic_Function_Reference to the PSyIR - representation. Since Fortran Intrinsics can be unary, binary or - nary this handler identifies the appropriate 'sub handler' by - examining the number of arguments present. + representation. :param node: node in fparser2 Parse Tree. - :type node: \ + :type node: :py:class:`fparser.two.Fortran2003.Intrinsic_Function_Reference` :param parent: Parent node of the PSyIR node we are constructing. :type parent: :py:class:`psyclone.psyir.nodes.Node` :returns: PSyIR representation of node - :rtype: :py:class:`psyclone.psyir.nodes.UnaryOperation` or \ - :py:class:`psyclone.psyir.nodes.BinaryOperation` or \ - :py:class:`psyclone.psyir.nodes.NaryOperation` or \ - :py:class:`psyclone.psyir.nodes.IntrinsicCall` + :rtype: :py:class:`psyclone.psyir.nodes.IntrinsicCall` - :raises NotImplementedError: if the form of the Fortran is not \ - supported. + :raises NotImplementedError: if an unsupported intrinsic is found. ''' - # First item is the name of the intrinsic - name = node.items[0].string.upper() - - # Fortran intrinsics are (or will be) treated as intrinsic calls. - if name.lower() in ["tiny", "huge"]: - # Intrinsics with no optional arguments - call = IntrinsicCall(self.intrinsics[name.lower()], parent=parent) + try: + intrinsic = IntrinsicCall.Intrinsic[node.items[0].string.upper()] + + if not intrinsic.optional_args: + # Intrinsics with no optional arguments + call = IntrinsicCall(intrinsic, parent=parent) + return self._process_args(node, call) + if intrinsic.name.lower() in ["minval", "maxval", "sum"]: + # Intrinsics with optional arguments require a + # canonicalise function + call = IntrinsicCall(intrinsic, parent=parent) + return self._process_args( + node, call, canonicalise=_canonicalise_minmaxsum) + # TODO #2302: We do not canonicalise the order of the + # arguments of the remaining intrinsics, but this means + # PSyIR won't be able to guarantee what each child is. + call = IntrinsicCall(intrinsic, parent=parent) return self._process_args(node, call) - if name.lower() in ["minval", "maxval", "sum"]: - # Intrinsics with optional arguments require a - # canonicalise function - call = IntrinsicCall(self.intrinsics[name.lower()], parent=parent) - return self._process_args( - node, call, canonicalise=_canonicalise_minmaxsum) - - # Treat all other intrinsics as Operations. - - # Now work out how many arguments it has - num_args = 0 - if len(node.items) > 1: - num_args = len(node.items[1].items) - - # We don't handle any intrinsics that don't have arguments - if num_args == 1: - return self._unary_op_handler(node, parent) - if num_args == 2: - return self._binary_op_handler(node, parent) - if num_args > 2: - return self._nary_op_handler(node, parent) - - # Intrinsic is not handled - this will result in a CodeBlock - raise NotImplementedError( - f"Operator '{name}' has no arguments but operators must have at " - f"least one.") + except KeyError as err: + raise NotImplementedError( + f"Intrinsic '{node.items[0].string}' is not supported" + ) from err def _name_handler(self, node, parent): ''' @@ -4328,12 +4157,12 @@ def _subscript_triplet_handler(self, node, parent): else: # There is no lower bound, it is implied. This is not # supported in the PSyIR so we create the equivalent code - # by using the PSyIR lbound function: + # by using the PSyIR lbound intrinsic function: # a(:...) becomes a(lbound(a,1):...) - lbound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - _copy_full_base_reference(parent), - Literal(dimension, integer_type)) + lbound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [_copy_full_base_reference(parent), + ("dim", Literal(dimension, integer_type))]) my_range.children.append(lbound) if node.children[1]: @@ -4341,12 +4170,12 @@ def _subscript_triplet_handler(self, node, parent): else: # There is no upper bound, it is implied. This is not # supported in the PSyIR so we create the equivalent code - # by using the PSyIR ubound function: + # by using the PSyIR ubound intrinsic function: # a(...:) becomes a(...:ubound(a,1)) - ubound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - _copy_full_base_reference(parent), - Literal(dimension, integer_type)) + ubound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [_copy_full_base_reference(parent), + ("dim", Literal(dimension, integer_type))]) my_range.children.append(ubound) if node.children[2]: diff --git a/src/psyclone/psyir/nodes/__init__.py b/src/psyclone/psyir/nodes/__init__.py index 066b700748..7b336214cf 100644 --- a/src/psyclone/psyir/nodes/__init__.py +++ b/src/psyclone/psyir/nodes/__init__.py @@ -55,7 +55,7 @@ from psyclone.psyir.nodes.array_of_structures_member import \ ArrayOfStructuresMember from psyclone.psyir.nodes.operation import Operation, UnaryOperation, \ - BinaryOperation, NaryOperation + BinaryOperation from psyclone.psyir.nodes.literal import Literal from psyclone.psyir.nodes.if_block import IfBlock from psyclone.psyir.nodes.intrinsic_call import IntrinsicCall @@ -117,7 +117,6 @@ 'Literal', 'Loop', 'Member', - 'NaryOperation', 'Node', 'OperandClause', 'Operation', diff --git a/src/psyclone/psyir/nodes/array_mixin.py b/src/psyclone/psyir/nodes/array_mixin.py index cf5e7b6f98..3c0d1d7d7e 100644 --- a/src/psyclone/psyir/nodes/array_mixin.py +++ b/src/psyclone/psyir/nodes/array_mixin.py @@ -46,6 +46,7 @@ from psyclone.psyir.nodes.call import Call from psyclone.psyir.nodes.codeblock import CodeBlock from psyclone.psyir.nodes.datanode import DataNode +from psyclone.psyir.nodes.intrinsic_call import IntrinsicCall from psyclone.psyir.nodes.literal import Literal from psyclone.psyir.nodes.member import Member from psyclone.psyir.nodes.operation import Operation, BinaryOperation @@ -131,19 +132,17 @@ def _is_bound_op(self, expr, bound_operator, index): :param expr: a PSyIR expression. :type expr: :py:class:`psyclone.psyir.nodes.Node` :param bound_operator: the particular bound operation. - :type bound_operator: \ - :py:class:`psyclone.psyir.nodes.operation.BinaryOperation.\ - Operator.LBOUND` or :py:class:`psyclone.psyir.nodes.operation.\ - BinaryOperation.Operator.UBOUND` + :type bound_operator: + :py:class:`psyclone.psyir.nodes.IntrinsicCall.Intrinsic.LBOUND | + :py:class:`psyclone.psyir.nodes.IntrinsicCall.Intrinsic.UBOUND :param int index: the bounds index. - :returns: True if the expr is in the expected form and False \ - otherwise. + :returns: True if the expr is in the expected form and False otherwise. :rtype: bool ''' - if (isinstance(expr, BinaryOperation) and - expr.operator == bound_operator): + if (isinstance(expr, IntrinsicCall) and + expr.intrinsic == bound_operator): # This is the expected bound if self.is_same_array(expr.children[0]): # The arrays match @@ -247,10 +246,12 @@ def _get_bound_expression(self, pos, bound): ref = Reference(root_ref.symbol) if bound == "lower": - return BinaryOperation.create(BinaryOperation.Operator.LBOUND, ref, - Literal(str(pos+1), INTEGER_TYPE)) - return BinaryOperation.create(BinaryOperation.Operator.UBOUND, ref, - Literal(str(pos+1), INTEGER_TYPE)) + return IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [ref, ("dim", Literal(str(pos+1), INTEGER_TYPE))]) + return IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [ref, ("dim", Literal(str(pos+1), INTEGER_TYPE))]) def get_lbound_expression(self, pos): ''' @@ -333,16 +334,16 @@ def _is_bound(self, index, bound_type): access_shape = self.indices[index] # Determine the appropriate (lower or upper) bound and check - # for a bounds operator. + # for a bounds intrinsic. if isinstance(access_shape, Range): if bound_type == "upper": - operator = BinaryOperation.Operator.UBOUND + intrinsic = IntrinsicCall.Intrinsic.UBOUND access_bound = access_shape.stop else: - operator = BinaryOperation.Operator.LBOUND + intrinsic = IntrinsicCall.Intrinsic.LBOUND access_bound = access_shape.start # Is this array access in the form of {UL}BOUND(array, index)? - if self._is_bound_op(access_bound, operator, index): + if self._is_bound_op(access_bound, intrinsic, index): return True else: access_bound = access_shape diff --git a/src/psyclone/psyir/nodes/array_reference.py b/src/psyclone/psyir/nodes/array_reference.py index 7329b1e29c..1b37d90a61 100644 --- a/src/psyclone/psyir/nodes/array_reference.py +++ b/src/psyclone/psyir/nodes/array_reference.py @@ -41,7 +41,7 @@ from psyclone.errors import GenerationError from psyclone.psyir.nodes.array_mixin import ArrayMixin from psyclone.psyir.nodes.literal import Literal -from psyclone.psyir.nodes.operation import BinaryOperation +from psyclone.psyir.nodes.intrinsic_call import IntrinsicCall from psyclone.psyir.nodes.ranges import Range from psyclone.psyir.nodes.reference import Reference from psyclone.psyir.symbols import (DataSymbol, DeferredType, UnknownType, @@ -102,12 +102,14 @@ def create(symbol, indices): array = ArrayReference(symbol) for ind, child in enumerate(indices): if child == ":": - lbound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol), Literal(f"{ind+1}", INTEGER_TYPE)) - ubound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(symbol), Literal(f"{ind+1}", INTEGER_TYPE)) + lbound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), + ("dim", Literal(f"{ind+1}", INTEGER_TYPE))]) + ubound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(symbol), + ("dim", Literal(f"{ind+1}", INTEGER_TYPE))]) my_range = Range.create(lbound, ubound) array.addchild(my_range) else: diff --git a/src/psyclone/psyir/nodes/call.py b/src/psyclone/psyir/nodes/call.py index 085ae9e532..25b27aba13 100644 --- a/src/psyclone/psyir/nodes/call.py +++ b/src/psyclone/psyir/nodes/call.py @@ -364,6 +364,14 @@ def is_pure(self): ''' return self._routine.is_pure + def is_available_on_device(self): + ''' + :returns: whether this intrinsic is available on an accelerated device. + :rtype: bool + + ''' + return False + @property def argument_names(self): ''' diff --git a/src/psyclone/psyir/nodes/intrinsic_call.py b/src/psyclone/psyir/nodes/intrinsic_call.py index 111f233c6b..aedfc2bb72 100644 --- a/src/psyclone/psyir/nodes/intrinsic_call.py +++ b/src/psyclone/psyir/nodes/intrinsic_call.py @@ -33,6 +33,7 @@ # ----------------------------------------------------------------------------- # Author: A. R. Porter, STFC Daresbury Lab # Modified: R. W. Ford, STFC Daresbury Lab +# Modified: S. Siso, STFC Daresbury Lab # ----------------------------------------------------------------------------- ''' This module contains the IntrinsicCall node implementation.''' @@ -46,9 +47,24 @@ from psyclone.psyir.nodes.reference import Reference from psyclone.psyir.symbols import IntrinsicSymbol - # pylint: disable=too-many-branches +# Named tuple for describing the attributes of each intrinsic +IAttr = namedtuple( + 'IAttr', 'name is_pure is_elemental is_inquiry required_args optional_args' +) +# Alternatively we could use an Enum to decrive the intrinsic types +# IntrinsicType = Enum('IntrinsicType', +# 'Atomic Collective Elemental Inquiry Pure Impure Transformational' +# ) +# And let the IntrinsicCall is_pure, is_elemental, ... do the conversion + +# Named tuple for describing the properties of the required arguments to +# a particular intrinsic. If there's no limit on the number of arguments +# then `max_count` will be None. +ArgDesc = namedtuple('ArgDesc', 'min_count max_count types') + + class IntrinsicCall(Call): ''' Node representing a call to an intrinsic routine (function or subroutine). This can be found as a standalone statement @@ -71,51 +87,659 @@ class IntrinsicCall(Call): #: the constructor (of the parent class). _symbol_type = IntrinsicSymbol - #: The intrinsics that can be represented by this node. - Intrinsic = Enum('Intrinsic', [ - 'ALLOCATE', 'DEALLOCATE', 'RANDOM_NUMBER', 'MINVAL', 'MAXVAL', "SUM", - "TINY", "HUGE"]) - #: Named tuple for describing the properties of the required arguments to - #: a particular intrinsic. If there's no limit on the number of arguments - #: then `max_count` will be None. - ArgDesc = namedtuple('ArgDesc', 'min_count max_count types') - #: List of ArgDesc objects describing the required arguments, indexed - #: by intrinsic name. - _required_args = {} - #: Dict of optional arguments, indexed by intrinsic name. Each optional - #: argument is described by an entry in the Dict. - _optional_args = {} - # The positional arguments to allocate must all be References (or - # ArrayReferences but they are a subclass of Reference). - _required_args[Intrinsic.ALLOCATE] = ArgDesc(1, None, Reference) - _optional_args[Intrinsic.ALLOCATE] = { - # Argument used to specify the shape of the object being allocated. - "mold": Reference, - # Argument specifying both shape and initial value(s) for the object - # being allocated. - "source": Reference, - # Integer variable given status value upon exit. - "stat": Reference, - # Variable in which message is stored upon error. (Requires that 'stat' - # also be provided otherwise the program will just abort upon error.) - "errmsg": Reference} - _required_args[Intrinsic.DEALLOCATE] = ArgDesc(1, None, Reference) - _optional_args[Intrinsic.DEALLOCATE] = {"stat": Reference} - _required_args[Intrinsic.RANDOM_NUMBER] = ArgDesc(1, 1, Reference) - _optional_args[Intrinsic.RANDOM_NUMBER] = {} - _required_args[Intrinsic.MINVAL] = ArgDesc(1, 1, DataNode) - _optional_args[Intrinsic.MINVAL] = { - "dim": DataNode, "mask": DataNode} - _required_args[Intrinsic.MAXVAL] = ArgDesc(1, 1, DataNode) - _optional_args[Intrinsic.MAXVAL] = { - "dim": DataNode, "mask": DataNode} - _required_args[Intrinsic.SUM] = ArgDesc(1, 1, DataNode) - _optional_args[Intrinsic.SUM] = { - "dim": DataNode, "mask": DataNode} - _required_args[Intrinsic.TINY] = ArgDesc(1, 1, (Reference, Literal)) - _optional_args[Intrinsic.TINY] = {} - _required_args[Intrinsic.HUGE] = ArgDesc(1, 1, (Reference, Literal)) - _optional_args[Intrinsic.HUGE] = {} + class Intrinsic(IAttr, Enum): + ''' Enum of all intrinsics with their attributes as values using the + IAttr namedtuple format: + + NAME = IAttr(name, is_pure, is_elemental, is_inquiry, + required_args, optional_args) + + Note that name is duplicated inside IAttr because each item in the + Enum must have a different value, and without the name that would + not be guaranteed. + + ''' + # Fortran special-case statements (technically not Fortran intrinsics + # but in PSyIR they are represented as Intrinsics) + ALLOCATE = IAttr( + 'ALLOCATE', False, False, False, + ArgDesc(1, None, Reference), + {"mold": Reference, "source": Reference, "stat": Reference, + "errmsg": Reference}) + DEALLOCATE = IAttr( + 'DEALLOCATE', False, False, False, + ArgDesc(1, None, Reference), {"stat": Reference}) + NULLIFY = IAttr( + 'NULLIFY', False, False, False, + ArgDesc(1, None, Reference), {}) + + # Fortran Intrinsics (from Fortran 2018 standard table 16.1) + ABS = IAttr( + 'ABS', True, True, False, + ArgDesc(1, 1, DataNode), {}) + ACHAR = IAttr( + 'ACHAR', True, True, False, + ArgDesc(1, 1, DataNode), {"kind": DataNode}) + ACOS = IAttr( + 'ACOS', True, True, False, + ArgDesc(1, 1, DataNode), {}) + ACOSH = IAttr( + 'ACOSH', True, True, False, + ArgDesc(1, 1, DataNode), {}) + ADJUSTL = IAttr( + 'ADJUSTL', True, True, False, + ArgDesc(1, 1, DataNode), {}) + ADJUSTR = IAttr( + 'ADJUSTR', True, True, False, + ArgDesc(1, 1, DataNode), {}) + AIMAG = IAttr( + 'AIMAG', True, True, False, + ArgDesc(1, 1, DataNode), {}) + AINT = IAttr( + 'AINT', True, True, False, + ArgDesc(1, 1, DataNode), {"kind": DataNode}) + ALL = IAttr( + 'ALL', True, False, False, + ArgDesc(1, 1, DataNode), {"dim": DataNode}) # ? + ALLOCATED = IAttr( + 'ALLOCATED', True, False, True, + ArgDesc(1, 1, DataNode), {}) + ANINT = IAttr( + 'ANINT', True, True, False, + ArgDesc(1, 1, DataNode), {"kind": DataNode}) + ANY = IAttr( + 'ANY', True, False, False, + ArgDesc(1, 1, DataNode), {"dim": DataNode}) # ? + ASIN = IAttr( + 'ASIN', True, True, False, + ArgDesc(1, 1, DataNode), {}) + ASINH = IAttr( + 'ASINH', True, True, False, + ArgDesc(1, 1, DataNode), {}) + ASSOCIATED = IAttr( + 'ASSOCIATED', False, False, True, + ArgDesc(1, 1, DataNode), {"target": DataNode}) + ATAN = IAttr( + 'ATAN', True, True, False, + ArgDesc(1, 2, DataNode), {}) + ATAN2 = IAttr( + 'ATAN2', True, True, False, + ArgDesc(2, 2, DataNode), {}) + ATANH = IAttr( + 'ATANH', True, True, False, + ArgDesc(1, 1, DataNode), {}) + ATOMIC_ADD = IAttr( + 'ATOMIC_ADD', True, True, False, + ArgDesc(2, 2, DataNode), {"stat": DataNode}) + ATOMIC_AND = IAttr( + 'ATOMIC_AND', True, True, False, + ArgDesc(2, 2, DataNode), {"stat": DataNode}) + ATOMIC_CAS = IAttr( + 'ATOMIC_CAS', True, True, False, + ArgDesc(2, 2, DataNode), {"stat": DataNode}) + ATOMIC_DEFINE = IAttr( + 'ATOMIC_DEFINE', True, True, False, + ArgDesc(2, 2, DataNode), {"stat": DataNode}) + ATOMIC_FETCH_ADD = IAttr( + 'ATOMIC_FETCH_ADD', True, True, False, + ArgDesc(3, 3, DataNode), {"stat": DataNode}) + ATOMIC_FETCH_AND = IAttr( + 'ATOMIC_FETCH_AND', True, True, False, + ArgDesc(3, 3, DataNode), {"stat": DataNode}) + ATOMIC_FETCH_OR = IAttr( + 'ATOMIC_FETCH_OR', True, True, False, + ArgDesc(3, 3, DataNode), {"stat": DataNode}) + ATOMIC_FETCH_XOR = IAttr( + 'ATOMIC_FETCH_XOR', True, True, False, + ArgDesc(3, 3, DataNode), {"stat": DataNode}) + ATOMIC_OR = IAttr( + 'ATOMIC_OR', True, True, False, + ArgDesc(2, 2, DataNode), {"stat": DataNode}) + ATOMIC_REF = IAttr( + 'ATOMIC_REF', True, True, False, + ArgDesc(2, 2, DataNode), {"stat": DataNode}) + ATOMIC_XOR = IAttr( + 'ATOMIC_XOR', True, True, False, + ArgDesc(2, 2, DataNode), {"stat": DataNode}) + BESSEL_J0 = IAttr( + 'BESSEL_J0', True, True, False, + ArgDesc(1, 1, DataNode), {}) + BESSEL_J1 = IAttr( + 'BESSEL_J1', True, True, False, + ArgDesc(1, 1, DataNode), {}) + BESSEL_JN = IAttr( + 'BESSEL_JN', True, None, False, + ArgDesc(2, 3, DataNode), {}) + BESSEL_Y0 = IAttr( + 'BESSEL_Y0', True, True, False, + ArgDesc(1, 1, DataNode), {}) + BESSEL_Y1 = IAttr( + 'BESSEL_Y1', True, True, False, + ArgDesc(1, 1, DataNode), {}) + BESSEL_YN = IAttr( + 'BESSEL_YN', True, None, False, + ArgDesc(2, 3, DataNode), {}) + BGE = IAttr( + 'BGE', True, True, False, + ArgDesc(2, 2, DataNode), {}) + BGT = IAttr( + 'BGT', True, True, False, + ArgDesc(2, 2, DataNode), {}) + BIT_SIZE = IAttr( + 'BIT_SIZE', True, False, True, + ArgDesc(1, 1, DataNode), {}) + BLE = IAttr( + 'BLE', True, True, False, + ArgDesc(2, 2, DataNode), {}) + BLT = IAttr( + 'BLT', True, True, False, + ArgDesc(2, 2, DataNode), {}) + BTEST = IAttr( + 'BTEST', True, True, False, + ArgDesc(2, 2, DataNode), {}) + CEILING = IAttr( + 'CEILING', True, True, False, + ArgDesc(1, 1, DataNode), {"kind": DataNode}) + CHAR = IAttr( + 'CHAR', True, True, False, + ArgDesc(1, 1, DataNode), {"kind": DataNode}) + CMPLX = IAttr( + 'CMPLX', True, True, False, + ArgDesc(1, 1, DataNode), {"Y": DataNode, "kind": DataNode}) + CO_BROADCAST = IAttr( + 'CO_BROADCAST', True, False, False, + ArgDesc(1, 2, DataNode), {"stat": DataNode, "errmsg": DataNode}) + CO_MAX = IAttr( + 'CO_MAX', True, False, False, + ArgDesc(1, 1, DataNode), + {"result_image": DataNode, "stat": DataNode, "errmsg": DataNode}) + CO_MIN = IAttr( + 'CO_MIN', True, False, False, + ArgDesc(1, 1, DataNode), + {"result_image": DataNode, "stat": DataNode, "errmsg": DataNode}) + CO_REDUCE = IAttr( + 'CO_REDUCE', True, False, False, + ArgDesc(1, 2, DataNode), + {"result_image": DataNode, "stat": DataNode, "errmsg": DataNode}) + CO_SUM = IAttr( + 'CO_SUM', True, False, False, + ArgDesc(1, 1, DataNode), + {"result_image": DataNode, "stat": DataNode, "errmsg": DataNode}) + COMMAND_ARGUMENT_COUNT = IAttr( + 'COMMAND_ARGUMENT_COUNT', True, False, False, + ArgDesc(0, 0, None), {}) + CONJG = IAttr( + 'CONJG', True, True, False, + ArgDesc(1, 1, DataNode), {}) + COS = IAttr( + 'COS', True, True, False, + ArgDesc(1, 1, DataNode), {}) + COSH = IAttr( + 'COSH', True, True, False, + ArgDesc(1, 1, DataNode), {}) + COSHAPE = IAttr( + 'COSHAPE', True, False, True, + ArgDesc(1, 1, DataNode), {"kind": DataNode}) + COUNT = IAttr( + 'COUNT', True, False, False, + ArgDesc(1, 1, DataNode), {"dim": DataNode, "kind": DataNode}) + CPU_TIME = IAttr( + 'CPU_TIME', False, False, False, + ArgDesc(1, 1, DataNode), {}) + CSHIFT = IAttr( + 'CSHIFT', True, False, False, + ArgDesc(2, 2, DataNode), {"dim": DataNode}) + DATE_AND_TIME = IAttr( + 'DATE_AND_TIME', False, False, False, + ArgDesc(0, 0, DataNode), + {"date": DataNode, "time": DataNode, + "zone": DataNode, "values": DataNode}) + DBLE = IAttr( + 'DBLE', True, True, False, + ArgDesc(1, 1, DataNode), {}) + DIGITS = IAttr( + 'DIGITS', True, False, True, + ArgDesc(1, 1, DataNode), {}) + DIM = IAttr( + 'DIM', True, True, False, + ArgDesc(2, 2, DataNode), {}) + DOT_PRODUCT = IAttr( + 'DOT_PRODUCT', True, False, False, + ArgDesc(2, 2, DataNode), {}) + DPROD = IAttr( + 'DPROD', True, True, False, + ArgDesc(2, 2, DataNode), {}) + DSHIFTL = IAttr( + 'DSHIFTL', True, True, False, + ArgDesc(3, 3, DataNode), {}) + DSHIFTR = IAttr( + 'DSHIFTR', True, True, False, + ArgDesc(3, 3, DataNode), {}) + EOSHIFT = IAttr( + 'EOSHIFT', True, False, False, + ArgDesc(2, 2, DataNode), {"boundary": DataNode, "dim": DataNode}) + EPSILON = IAttr( + 'EPSILON', True, False, True, + ArgDesc(1, 1, DataNode), {}) + ERF = IAttr( + 'ERF', True, True, False, + ArgDesc(1, 1, DataNode), {}) + ERFC = IAttr( + 'ERFC', True, True, False, + ArgDesc(1, 1, DataNode), {}) + ERFC_SCALED = IAttr( + 'ERFC_SCALED', True, True, False, + ArgDesc(1, 1, DataNode), {}) + EVENT_QUERY = IAttr( + 'EVENT_QUERY', False, False, False, + ArgDesc(2, 2, DataNode), {"stat": DataNode}) + EXECUTE_COMMAND_LINE = IAttr( + 'EXECUTE_COMMAND_LINE', False, False, False, + ArgDesc(2, 2, DataNode), + {"wait": DataNode, "exitstat": DataNode, + "cmdstat": DataNode, "cmdmsg": DataNode}) + EXP = IAttr( + 'EXP', True, True, False, + ArgDesc(1, 1, DataNode), {}) + EXPONENT = IAttr( + 'EXPONENT', True, True, False, + ArgDesc(1, 1, DataNode), {}) + EXTENDS_TYPE_OF = IAttr( + 'EXTENDS_TYPE_OF', True, False, True, + ArgDesc(2, 2, DataNode), {}) + FAILED_IMAGES = IAttr( + 'FAILED_IMAGES', False, False, False, + ArgDesc(0, 0, DataNode), {"team": DataNode, "kind": DataNode}) + FINDLOC = IAttr( + 'FINDLOC', True, False, False, + ArgDesc(2, 3, DataNode), + {"mask": DataNode, "kind": DataNode, "back": DataNode}) + FLOAT = IAttr( + 'FLOAT', True, True, False, + ArgDesc(1, 1, DataNode), {}) + FLOOR = IAttr( + 'FLOOR', True, True, False, + ArgDesc(1, 1, DataNode), {"kind": DataNode}) + FRACTION = IAttr( + 'FRACTION', True, True, False, + ArgDesc(1, 1, DataNode), {}) + GAMMA = IAttr( + 'GAMMA', True, True, False, + ArgDesc(1, 1, DataNode), {}) + GET_COMMAND = IAttr( + 'GET_COMMAND', False, False, False, + ArgDesc(0, 0, DataNode), + {"command": DataNode, "length": DataNode, + "status": DataNode, "errmsg": DataNode}) + GET_COMMAND_ARGUMENT = IAttr( + 'GET_COMMAND_ARGUMENT', False, False, False, + ArgDesc(1, 1, DataNode), + {"value": DataNode, "length": DataNode, + "status": DataNode, "errmsg": DataNode}) + GET_ENVIRONMENT_VARIABLE = IAttr( + 'GET_ENVIRONMENT_VARIABLE', False, False, False, + ArgDesc(1, 1, DataNode), + {"value": DataNode, "length": DataNode, "status": DataNode, + "trim_name": DataNode, "errmsg": DataNode}) + GET_TEAM = IAttr( + 'GET_TEAM', True, False, False, + ArgDesc(0, 0, DataNode), {"level": DataNode}) + HUGE = IAttr( + 'HUGE', True, False, True, + ArgDesc(1, 1, (Reference, Literal)), {}) + HYPOT = IAttr( + 'HYPOT', True, True, False, + ArgDesc(2, 2, (DataNode)), {}) + IACHAR = IAttr( + 'IACHAR', True, True, False, + ArgDesc(1, 1, (DataNode)), {"kind": DataNode}) + IALL = IAttr( + 'IALL', True, False, False, + ArgDesc(1, 1, (DataNode)), {"dim": DataNode, "kind": DataNode}) + IAND = IAttr( + 'IAND', True, True, False, + ArgDesc(2, 2, (DataNode)), {}) + IANY = IAttr( + 'IANY', True, False, False, + ArgDesc(1, 1, (DataNode)), {"dim": DataNode, "kind": DataNode}) + IBCLR = IAttr( + 'IBCLR', True, True, False, + ArgDesc(2, 2, (DataNode)), {}) + IBITS = IAttr( + 'IBITS', True, True, False, + ArgDesc(3, 3, (DataNode)), {}) + IBSET = IAttr( + 'IBSET', True, True, False, + ArgDesc(2, 2, (DataNode)), {}) + ICHAR = IAttr( + 'ICHAR', True, True, False, + ArgDesc(1, 1, (DataNode)), {"kind": DataNode}) + IEOR = IAttr( + 'IEOR', True, True, False, + ArgDesc(2, 2, (DataNode)), {}) + IMAGE_INDEX = IAttr( + 'IMAGE_INDEX', True, False, True, + ArgDesc(2, 3, (DataNode)), {}) + IMAGE_STATUS = IAttr( + 'IMAGE_STATUS', True, False, False, + ArgDesc(1, 1, (DataNode)), {"team": DataNode}) + INDEX = IAttr( + 'INDEX', True, True, False, + ArgDesc(2, 2, (DataNode)), {"back": DataNode, "kind": DataNode}) + INT = IAttr( + 'INT', True, True, False, + ArgDesc(1, 1, (DataNode)), {"kind": DataNode}) + IOR = IAttr( + 'IOR', True, True, False, + ArgDesc(2, 2, (DataNode)), {}) + IPARITY = IAttr( + 'IPARITY', True, False, False, + ArgDesc(1, 2, (DataNode)), {"mask": DataNode}) + ISHFT = IAttr( + 'ISHFT', True, True, False, + ArgDesc(2, 2, (DataNode)), {}) + ISHFTC = IAttr( + 'ISHFT', True, True, False, + ArgDesc(2, 2, (DataNode)), {"size": DataNode}) + IS_CONTIGUOUS = IAttr( + 'IS_CONTIGUOUS', True, False, True, + ArgDesc(1, 1, (DataNode)), {}) + IS_IOSTAT_END = IAttr( + 'IS_IOSTAT_END', True, True, False, + ArgDesc(1, 1, (DataNode)), {}) + IS_IOSTAT_EOR = IAttr( + 'IS_IOSTAT_EOR', True, True, False, + ArgDesc(1, 1, (DataNode)), {}) + KIND = IAttr( + 'KIND', True, False, True, + ArgDesc(1, 1, (DataNode)), {}) + LBOUND = IAttr( + 'LBOUND', True, False, True, + ArgDesc(1, 1, (DataNode)), {"dim": DataNode, "kind": DataNode}) + LCOBOUND = IAttr( + 'LCOBOUND', True, False, True, + ArgDesc(1, 1, (DataNode)), {"dim": DataNode, "kind": DataNode}) + LEADZ = IAttr( + 'LEADZ', True, True, False, + ArgDesc(1, 1, (DataNode)), {}) + LEN = IAttr( + 'LEN', True, False, True, + ArgDesc(1, 1, (DataNode)), {"kind": DataNode}) + LEN_TRIM = IAttr( + 'LEN_TRIM', True, True, False, + ArgDesc(1, 1, (DataNode)), {"kind": DataNode}) + LGE = IAttr( + 'LGE', True, True, False, + ArgDesc(2, 2, (DataNode)), {}) + LGT = IAttr( + 'LGT', True, True, False, + ArgDesc(2, 2, (DataNode)), {}) + LLE = IAttr( + 'LLE', True, True, False, + ArgDesc(2, 2, (DataNode)), {}) + LLT = IAttr( + 'LLT', True, True, False, + ArgDesc(2, 2, (DataNode)), {}) + LOG = IAttr( + 'LOG', True, True, False, + ArgDesc(1, 1, (DataNode)), {}) + LOG_GAMMA = IAttr( + 'LOG_GAMMA', True, True, False, + ArgDesc(1, 1, (DataNode)), {}) + LOG10 = IAttr( + 'LOG10', True, True, False, + ArgDesc(1, 1, (DataNode)), {}) + LOGICAL = IAttr( + 'LOGICAL', True, True, False, + ArgDesc(1, 1, (DataNode)), {"kind": DataNode}) + MASKL = IAttr( + 'MASKL', True, True, False, + ArgDesc(1, 1, (DataNode)), {"kind": DataNode}) + MASKR = IAttr( + 'MASKR', True, True, False, + ArgDesc(1, 1, (DataNode)), {"kind": DataNode}) + MATMUL = IAttr( + 'MATMUL', True, False, False, + ArgDesc(2, 2, DataNode), {}) + MAX = IAttr( + 'MAX', True, True, False, + ArgDesc(2, None, DataNode), {}) + MAXEXPONENT = IAttr( + 'MAXEXPONENT', True, False, True, + ArgDesc(1, 1, DataNode), {}) + MAXLOC = IAttr( + 'MAXLOC', True, False, False, + ArgDesc(1, 2, DataNode), + {"dim": DataNode, "mask": DataNode, "kind": DataNode, + "back": DataNode}) + MAXVAL = IAttr( + 'MAXVAL', True, False, False, + ArgDesc(1, 1, DataNode), + {"dim": DataNode, "mask": DataNode}) + MERGE = IAttr( + 'MERGE', True, True, False, + ArgDesc(3, 3, DataNode), {}) + MERGE_BITS = IAttr( + 'MERGE_BITS', True, True, False, + ArgDesc(3, 3, DataNode), {}) + MIN = IAttr( + 'MIN', True, True, False, + ArgDesc(2, None, DataNode), {}) + MINEXPONENT = IAttr( + 'MINEXPONENT', True, False, True, + ArgDesc(1, 1, DataNode), {}) + MINLOC = IAttr( + 'MINLOC', True, False, False, + ArgDesc(1, 2, DataNode), + {"dim": DataNode, "mask": DataNode, "kind": DataNode, + "back": DataNode}) + MINVAL = IAttr( + 'MINVAL', True, False, False, + ArgDesc(1, 1, DataNode), + {"dim": DataNode, "mask": DataNode}) + MOD = IAttr( + 'MOD', True, True, False, + ArgDesc(2, 2, DataNode), {}) + MODULO = IAttr( + 'MODULO', True, True, False, + ArgDesc(2, 2, DataNode), {}) + MOVE_ALLOC = IAttr( + 'MOVE_ALLOC', False, False, False, + ArgDesc(2, 2, DataNode), {"stat": DataNode, "errmsg": DataNode}) + MVBITS = IAttr( + 'MVBITS', True, True, False, + ArgDesc(5, 5, DataNode), {}) + NEAREST = IAttr( + 'NEAREST', True, True, False, + ArgDesc(2, 2, DataNode), {}) + NEW_LINE = IAttr( + 'NEW_LINE', True, True, False, + ArgDesc(1, 1, DataNode), {}) + NINT = IAttr( + 'NINT', True, True, False, + ArgDesc(1, 1, DataNode), {"kind": DataNode}) + NORM2 = IAttr( + 'NORM2', True, False, False, + ArgDesc(1, 2, DataNode), {}) + NOT = IAttr( + 'NOT', True, True, False, + ArgDesc(1, 1, DataNode), {}) + NULL = IAttr( + 'NULL', True, False, False, + ArgDesc(0, 0, DataNode), {"mold": DataNode}) + NUM_IMAGES = IAttr( + 'NUM_IMAGES', True, False, False, + ArgDesc(0, 1, DataNode), {}) + OUT_OF_RANGE = IAttr( + 'OUT_OF_RANGE', True, True, False, + ArgDesc(2, 2, DataNode), {"round": DataNode}) + PACK = IAttr( + 'PACK', True, False, False, + ArgDesc(2, 2, DataNode), {"vector": DataNode}) + PARITY = IAttr( + 'PARITY', True, False, False, + ArgDesc(1, 2, DataNode), {}) + POPCNT = IAttr( + 'POPCNT', True, True, False, + ArgDesc(1, 1, DataNode), {}) + POPPAR = IAttr( + 'POPPAR', True, True, False, + ArgDesc(1, 1, DataNode), {}) + PRECISION = IAttr( + 'PRECISON', True, False, True, + ArgDesc(1, 1, DataNode), {}) + PRESENT = IAttr( + 'PRESENT', True, False, True, + ArgDesc(1, 1, DataNode), {}) + PRODUCT = IAttr( + 'PRODUCT', True, False, False, + ArgDesc(1, 1, DataNode), {"dim": DataNode, "mask": DataNode}) + RADIX = IAttr( + 'RADIX', True, False, True, + ArgDesc(1, 1, DataNode), {}) + RANDOM_INIT = IAttr( + 'RANDOM_INIT', False, False, False, + ArgDesc(2, 2, DataNode), {}) + RANDOM_NUMBER = IAttr( + 'RANDOM_NUMBER', False, False, False, + ArgDesc(1, 1, Reference), {}) + RANDOM_SEED = IAttr( + 'RANDOM_SEED', False, False, False, + ArgDesc(0, 0, Reference), + {"size": DataNode, "put": DataNode, "Get": DataNode}) + RANGE = IAttr( + 'RANGE', True, False, True, + ArgDesc(1, 1, Reference), {}) + RANK = IAttr( + 'RANK', True, False, True, + ArgDesc(1, 1, Reference), {}) + REAL = IAttr( + 'REAL', True, True, False, + ArgDesc(1, 1, Reference), {"kind": DataNode}) + REDUCE = IAttr( + 'REDUCE', True, False, False, + ArgDesc(2, 3, Reference), + {"mask": DataNode, "identity": DataNode, "ordered": DataNode}) + REPEAT = IAttr( + 'REPEAT', True, False, False, + ArgDesc(2, 2, Reference), {}) + RESHAPE = IAttr( + 'RESHAPE', True, False, False, + ArgDesc(2, 2, Reference), {"pad": DataNode, "order": DataNode}) + RRSPACING = IAttr( + 'RRSPACING', True, True, False, + ArgDesc(1, 1, Reference), {}) + SAME_TYPE_AS = IAttr( + 'SAME_TYPE_AS', True, False, True, + ArgDesc(2, 2, Reference), {}) + SCALE = IAttr( + 'SCALE', True, True, False, + ArgDesc(2, 2, Reference), {}) + SCAN = IAttr( + 'SCAN', True, True, False, + ArgDesc(2, 2, Reference), {"back": DataNode, "kind": DataNode}) + SELECTED_CHAR_KIND = IAttr( + 'SELECTED_CHAR_KIND', True, False, False, + ArgDesc(1, 1, Reference), {}) + SELECTED_INT_KIND = IAttr( + 'SELECTED_INT_KIND', True, False, False, + ArgDesc(1, 1, Reference), {}) + SELECTED_REAL_KIND = IAttr( + 'SELECTED_REAL_KIND', True, False, False, + ArgDesc(0, 0, Reference), + {"P": DataNode, "R": DataNode, "radix": DataNode}) + SET_EXPONENT = IAttr( + 'SET_EXPONENT', True, True, False, + ArgDesc(2, 2, Reference), {}) + SHAPE = IAttr( + 'SHAPE', True, False, True, + ArgDesc(1, 1, Reference), {"kind": DataNode}) + SHIFTA = IAttr( + 'SHIFTA', True, True, False, + ArgDesc(2, 2, Reference), {}) + SHIFTL = IAttr( + 'SHIFTL', True, True, False, + ArgDesc(2, 2, Reference), {}) + SHIFTR = IAttr( + 'SHIFTR', True, True, False, + ArgDesc(2, 2, Reference), {}) + SIGN = IAttr( + 'SIGN', True, True, False, + ArgDesc(2, 2, DataNode), {}) + SIN = IAttr( + 'SIN', True, True, False, + ArgDesc(1, 1, DataNode), {}) + SINH = IAttr( + 'SINH', True, True, False, + ArgDesc(1, 1, DataNode), {}) + SIZE = IAttr( + 'SIZE', True, False, True, + ArgDesc(1, 1, DataNode), {"dim": DataNode, "kind": DataNode}) + SPACING = IAttr( + 'SPACING', True, True, False, + ArgDesc(1, 1, DataNode), {}) + SPREAD = IAttr( + 'SPREAD', True, False, False, + ArgDesc(3, 3, DataNode), {}) + SQRT = IAttr( + 'SQRT', True, True, False, + ArgDesc(1, 1, DataNode), {}) + STOPPED_IMAGES = IAttr( + 'STOPPED_IMAGES', False, False, False, + ArgDesc(0, 0, DataNode), {"team": DataNode, "kind": DataNode}) + STORAGE_SIZE = IAttr( + 'STORAGE_SIZE', True, False, True, + ArgDesc(1, 1, DataNode), {"kind": DataNode}) + SUM = IAttr( + 'SUM', True, False, False, + ArgDesc(1, 1, DataNode), {"dim": DataNode, "mask": DataNode}) + SYSTEM_CLOCK = IAttr( + 'SYSTEM_CLOCK', False, False, False, + ArgDesc(0, 0, DataNode), + {"count": DataNode, "count_rate": DataNode, "count_max": DataNode}) + TAN = IAttr( + 'TAN', True, True, False, + ArgDesc(1, 1, DataNode), {}) + TANH = IAttr( + 'TANH', True, True, False, + ArgDesc(1, 1, DataNode), {}) + TEAM_IMAGE = IAttr( + 'TEAM_IMAGE', True, False, False, + ArgDesc(0, 0, DataNode), {"team": DataNode}) + THIS_IMAGE = IAttr( + 'THIS_IMAGE', True, False, False, + ArgDesc(0, 0, DataNode), + {"coarray": DataNode, "team": DataNode, "dim": DataNode}) + TINY = IAttr( + 'TINY', True, False, True, + ArgDesc(1, 1, (Reference, Literal)), {}) + TRAILZ = IAttr( + 'TRAILZ', True, True, False, + ArgDesc(1, 1, DataNode), {}) + TRANSFER = IAttr( + 'TRANSFER', True, False, False, + ArgDesc(2, 2, DataNode), {"size": DataNode}) + TRANSPOSE = IAttr( + 'TRANSPOSE', True, False, False, + ArgDesc(1, 1, DataNode), {}) + TRIM = IAttr( + 'TRIM', True, False, False, + ArgDesc(1, 1, DataNode), {}) + UBOUND = IAttr( + 'UBOUND', True, False, True, + ArgDesc(1, 1, DataNode), {"dim": DataNode, "kind": DataNode}) + UCOBOUND = IAttr( + 'UCOBOUND', True, False, True, + ArgDesc(1, 1, DataNode), {"dim": DataNode, "kind": DataNode}) + UNPACK = IAttr( + 'UNPACK', True, False, False, + ArgDesc(3, 3, DataNode), {}) + VERIFY = IAttr( + 'TRIM', True, True, False, + ArgDesc(2, 2, DataNode), {"back": DataNode, "kind": DataNode}) + + def __hash__(self): + return hash(self.name) def __init__(self, routine, **kwargs): if not isinstance(routine, Enum) or routine not in self.Intrinsic: @@ -128,8 +752,8 @@ def __init__(self, routine, **kwargs): super().__init__( IntrinsicSymbol( routine.name, - is_elemental=(routine in ELEMENTAL_INTRINSICS), - is_pure=(routine in PURE_INTRINSICS)), + is_elemental=routine.is_elemental, + is_pure=routine.is_pure), **kwargs) self._intrinsic = routine @@ -143,6 +767,38 @@ def intrinsic(self): ''' return self._intrinsic + # This is not part of the intrinsic enum, because its ValueError could + # change for different devices, and in the future we may want to pass + # a device/arch/compiler parameter or look at the configuration file. + # Currently it is inspired from: https://docs.nvidia.com/hpc-sdk/ + # compilers/hpc-compilers-user-guide/#acc-fort-intrin-sum + # But that list is incomplete (e.g. SUM is supported and not listed) + def is_available_on_device(self): + ''' + :returns: whether this intrinsic is available on an accelerated device. + :rtype: bool + + ''' + return self.intrinsic in ( + IntrinsicCall.Intrinsic.ABS, IntrinsicCall.Intrinsic.ACOS, + IntrinsicCall.Intrinsic.AINT, IntrinsicCall.Intrinsic.ANINT, + IntrinsicCall.Intrinsic.ASIN, IntrinsicCall.Intrinsic.ATAN, + IntrinsicCall.Intrinsic.ATAN2, IntrinsicCall.Intrinsic.COS, + IntrinsicCall.Intrinsic.COSH, IntrinsicCall.Intrinsic.DBLE, + IntrinsicCall.Intrinsic.DPROD, IntrinsicCall.Intrinsic.EXP, + IntrinsicCall.Intrinsic.IAND, IntrinsicCall.Intrinsic.IEOR, + IntrinsicCall.Intrinsic.INT, IntrinsicCall.Intrinsic.IOR, + IntrinsicCall.Intrinsic.LOG, IntrinsicCall.Intrinsic.LOG10, + IntrinsicCall.Intrinsic.MAX, IntrinsicCall.Intrinsic.MIN, + IntrinsicCall.Intrinsic.MOD, IntrinsicCall.Intrinsic.NINT, + IntrinsicCall.Intrinsic.NOT, IntrinsicCall.Intrinsic.REAL, + IntrinsicCall.Intrinsic.SIGN, IntrinsicCall.Intrinsic.SIN, + IntrinsicCall.Intrinsic.SINH, IntrinsicCall.Intrinsic.SQRT, + IntrinsicCall.Intrinsic.TAN, IntrinsicCall.Intrinsic.TANH, + # The one below are not documented on nvidia compiler + IntrinsicCall.Intrinsic.SUM, IntrinsicCall.Intrinsic.LBOUND, + IntrinsicCall.Intrinsic.UBOUND) + @classmethod def create(cls, routine, arguments): '''Create an instance of this class given the type of routine and a @@ -179,15 +835,7 @@ def create(cls, routine, arguments): f"IntrinsicCall.create() 'arguments' argument should be a " f"list but found '{type(arguments).__name__}'") - if cls._optional_args[routine]: - optional_arg_names = sorted(list( - cls._optional_args[routine].keys())) - else: - optional_arg_names = [] - # Validate the supplied arguments. - reqd_args = cls._required_args[routine] - opt_args = cls._optional_args[routine] last_named_arg = None pos_arg_count = 0 for arg in arguments: @@ -199,42 +847,52 @@ def create(cls, routine, arguments): f"a {type(arg[0]).__name__} instead of a str.") name = arg[0].lower() last_named_arg = name - if not optional_arg_names: - raise ValueError( - f"The '{routine.name}' intrinsic does not support " - f"any optional arguments but got '{name}'.") - if name not in optional_arg_names: - raise ValueError( - f"The '{routine.name}' intrinsic supports the " - f"optional arguments {optional_arg_names} but got " - f"'{name}'") - if not isinstance(arg[1], opt_args[name]): - raise TypeError( - f"The optional argument '{name}' to intrinsic " - f"'{routine.name}' must be of type " - f"'{opt_args[name].__name__}' but got " - f"'{type(arg[1]).__name__}'") + # TODO #2302: For now we disable the positional arguments + # checks because this does not consider that positional + # arguments can be also found by name, and we don't have + # sufficient information to validate them. + # if not optional_arg_names: + # raise ValueError( + # f"The '{routine.name}' intrinsic does not support " + # f"any optional arguments but got '{name}'.") + # if name not in optional_arg_names: + # raise ValueError( + # f"The '{routine.name}' intrinsic supports the " + # f"optional arguments {optional_arg_names} but got " + # f"'{name}'") + if name in routine.optional_args: + if not isinstance(arg[1], routine.optional_args[name]): + raise TypeError( + f"The optional argument '{name}' to intrinsic " + f"'{routine.name}' must be of type " + f"'{routine.optional_args[name].__name__}' but got" + f" '{type(arg[1]).__name__}'") + else: + # If it not in the optional_args list it must be positional + pos_arg_count += 1 else: if last_named_arg: raise ValueError( f"Found a positional argument *after* a named " f"argument ('{last_named_arg}'). This is invalid.'") - if not isinstance(arg, reqd_args.types): + if not isinstance(arg, routine.required_args.types): raise TypeError( f"The '{routine.name}' intrinsic requires that " - f"positional arguments be of type '{reqd_args.types}' " + f"positional arguments be of type " + f"'{routine.required_args.types}' " f"but got a '{type(arg).__name__}'") pos_arg_count += 1 - if ((reqd_args.max_count is not None and - pos_arg_count > reqd_args.max_count) - or pos_arg_count < reqd_args.min_count): + if ((routine.required_args.max_count is not None and + pos_arg_count > routine.required_args.max_count) + or pos_arg_count < routine.required_args.min_count): msg = f"The '{routine.name}' intrinsic requires " - if reqd_args.max_count is not None and reqd_args.max_count > 0: - msg += (f"between {reqd_args.min_count} and " - f"{reqd_args.max_count} ") + if (routine.required_args.max_count is not None and + routine.required_args.max_count > 0): + msg += (f"between {routine.required_args.min_count} and " + f"{routine.required_args.max_count} ") else: - msg += f"at least {reqd_args.min_count} " + msg += f"at least {routine.required_args.min_count} " msg += f"arguments but got {len(arguments)}." raise ValueError(msg) @@ -249,6 +907,51 @@ def create(cls, routine, arguments): return call + def reference_accesses(self, var_accesses): + '''Get all reference access information from this node. + If the 'COLLECT-ARRAY-SHAPE-READS' options is set, it will report array + accesses used as first parameter in 'inquiry intrinsics' like + `lbound`, `ubound`, or `size` as 'read' accesses. + + :param var_accesses: VariablesAccessInfo instance that stores the + information about variable accesses. + :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo` + + ''' + if (self.intrinsic.is_inquiry and not + var_accesses.options("COLLECT-ARRAY-SHAPE-READS")): + # If this is an inquiry access (which doesn't actually access the + # value) and we haven't explicitly requested them, ignore the + # inquired variables, which are always the first argument. + for child in self._children[1:]: + child.reference_accesses(var_accesses) + else: + for child in self._children: + child.reference_accesses(var_accesses) + + # TODO #2102: Maybe the two properties below can be removed if intrinsic + # is a symbol, as they would act as the super() implementation. + @property + def is_elemental(self): + ''' + :returns: whether the routine being called is elemental (provided with + an input array it will apply the operation individually to each of + the array elements and return an array with the results). If this + information is not known then it returns None. + :rtype: NoneType | bool + ''' + return self.intrinsic.is_elemental + + @property + def is_pure(self): + ''' + :returns: whether the routine being called is pure (guaranteed to + return the same result when provided with the same argument + values). If this information is not known then it returns None. + :rtype: NoneType | bool + ''' + return self.intrinsic.is_pure + # TODO #658 this can be removed once we have support for determining the # type of a PSyIR expression. @@ -256,16 +959,3 @@ def create(cls, routine, arguments): REDUCTION_INTRINSICS = [ IntrinsicCall.Intrinsic.SUM, IntrinsicCall.Intrinsic.MINVAL, IntrinsicCall.Intrinsic.MAXVAL] - -# Intrinsics that, provided with an input array, apply their operation -# individually to each of the array elements and return an array with -# the results. -ELEMENTAL_INTRINSICS = [] - -# Intrinsics that are 'pure' functions. Given the same input arguments, a pure -# function will always return the same value. -PURE_INTRINSICS = [IntrinsicCall.Intrinsic.SUM, - IntrinsicCall.Intrinsic.MINVAL, - IntrinsicCall.Intrinsic.MAXVAL, - IntrinsicCall.Intrinsic.TINY, - IntrinsicCall.Intrinsic.HUGE] diff --git a/src/psyclone/psyir/nodes/operation.py b/src/psyclone/psyir/nodes/operation.py index e6a7a3481d..8a6978bf48 100644 --- a/src/psyclone/psyir/nodes/operation.py +++ b/src/psyclone/psyir/nodes/operation.py @@ -41,7 +41,6 @@ from abc import ABCMeta from enum import Enum -import re from psyclone.errors import GenerationError from psyclone.psyir.nodes.datanode import DataNode @@ -65,9 +64,7 @@ class Operation(DataNode, metaclass=ABCMeta): # Must be overridden in sub-class to hold an Enumeration of the Operators # that it can represent. Operator = object - _non_elemental_ops = [] - # Textual description of the node. - _text_name = "Operation" + # Colour of the node in a view tree. _colour = "blue" def __init__(self, operator, parent=None): @@ -79,115 +76,6 @@ def __init__(self, operator, parent=None): f"{type(self).__name__}.Operator but found " f"{type(operator).__name__}.") self._operator = operator - self._argument_names = [] - - def append_named_arg(self, name, arg): - '''Append a named argument to this operation. - - :param name: the argument name. - :type name: Optional[str] - :param arg: the argument expression. - :type arg: :py:class:`psyclone.psyir.nodes.DataNode` - - :raises ValueError: if the name argument is already used \ - for an existing argument. - - ''' - self._validate_name(name) - if name is not None: - for check_name in self.argument_names: - if check_name and check_name.lower() == name.lower(): - raise ValueError( - f"The value of the name argument ({name}) in " - f"'append_named_arg' in the 'Operator' node is " - f"already used for a named argument.") - self._argument_names.append((id(arg), name)) - self.children.append(arg) - - def insert_named_arg(self, name, arg, index): - '''Insert a named argument to the operation. - - :param name: the argument name. - :type name: Optional[str] - :param arg: the argument expression. - :type arg: :py:class:`psyclone.psyir.nodes.DataNode` - :param int index: where in the argument list to insert the \ - named argument. - - :raises ValueError: if the name argument is already used \ - for an existing argument. - :raises TypeError: if the index argument is the wrong type. - - ''' - self._validate_name(name) - if name is not None: - for check_name in self.argument_names: - if check_name and check_name.lower() == name.lower(): - raise ValueError( - f"The value of the name argument ({name}) in " - f"'insert_named_arg' in the 'Operator' node is " - f"already used for a named argument.") - if not isinstance(index, int): - raise TypeError( - f"The 'index' argument in 'insert_named_arg' in the " - f"'Operator' node should be an int but found " - f"{type(index).__name__}.") - self._argument_names.insert(index, (id(arg), name)) - self.children.insert(index, arg) - - def replace_named_arg(self, existing_name, arg): - '''Replace one named argument node with another node keeping the - same name. - - :param str existing_name: the argument name. - :param arg: the argument expression. - :type arg: :py:class:`psyclone.psyir.nodes.DataNode` - - :raises TypeError: if the name argument is the wrong type. - :raises ValueError: if the name argument is already used \ - for an existing argument. - :raises TypeError: if the index argument is the wrong type. - - ''' - if not isinstance(existing_name, str): - raise TypeError( - f"The 'name' argument in 'replace_named_arg' in the " - f"'Operation' node should be a string, but found " - f"{type(existing_name).__name__}.") - index = 0 - for _, name in self._argument_names: - if name is not None and name.lower() == existing_name: - break - index += 1 - else: - raise ValueError( - f"The value of the existing_name argument ({existing_name}) " - f"in 'replace_named_arg' in the 'Operation' node was not found" - f" in the existing arguments.") - self.children[index] = arg - self._argument_names[index] = (id(arg), existing_name) - - @staticmethod - def _validate_name(name): - '''Utility method that checks that the supplied name has a valid - format. - - :param name: the name to check. - :type name: Optional[str] - - :raises TypeError: if the name is not a string or None. - :raises ValueError: if this is not a valid name. - - ''' - if name is None: - return - if not isinstance(name, str): - raise TypeError( - f"A name should be a string or None, but found " - f"{type(name).__name__}.") - if not re.match(r'^[a-zA-Z]\w*$', name): - raise ValueError( - f"Invalid name '{name}' found.") def __eq__(self, other): '''Checks whether two Operations are equal. Operations are equal @@ -201,7 +89,6 @@ def __eq__(self, other): ''' is_eq = super().__eq__(other) is_eq = is_eq and self.operator == other.operator - is_eq = is_eq and self.argument_names == other.argument_names return is_eq @@ -218,6 +105,16 @@ def operator(self): ''' return self._operator + def __str__(self): + result = f"{self.node_str(False)}\n" + for entity in self._children: + result += f"{str(entity)}\n" + + # Delete last line break + if result[-1] == "\n": + result = result[:-1] + return result + def node_str(self, colour=True): ''' Construct a text representation of this node, optionally with control @@ -231,78 +128,6 @@ def node_str(self, colour=True): return self.coloured_name(colour) + \ "[operator:'" + self._operator.name + "']" - @property - def argument_names(self): - ''' - :returns: a list containing the names of named arguments. If the \ - entry is None then the argument is a positional argument. - :rtype: List[Optional[str]] - ''' - self._reconcile() - return [entry[1] for entry in self._argument_names] - - def _reconcile(self): - '''update the _argument_names values in case child arguments have been - removed, or added. - - ''' - new_argument_names = [] - for child in self.children: - for arg in self._argument_names: - if id(child) == arg[0]: - new_argument_names.append(arg) - break - else: - new_argument_names.append((id(child), None)) - self._argument_names = new_argument_names - - @property - def is_elemental(self): - ''' - :returns: whether this operation is elemental (provided with an input \ - array it will apply the operation individually to each of the \ - array elements and return an array with the results). - :rtype: bool - ''' - return self.operator not in self._non_elemental_ops - - def __str__(self): - result = f"{self.node_str(False)}\n" - for idx, entity in enumerate(self._children): - if self.argument_names[idx]: - result += f"{self.argument_names[idx]}={str(entity)}\n" - else: - result += f"{str(entity)}\n" - - # Delete last line break - if result[-1] == "\n": - result = result[:-1] - return result - - def copy(self): - '''Return a copy of this node. This is a bespoke implementation for - Operation nodes that ensures that any internal id's are - consistent before and after copying. - - :returns: a copy of this node and its children. - :rtype: :py:class:`psyclone.psyir.node.Node` - - ''' - # ensure _argument_names is consistent with actual arguments - # before copying. - # pylint: disable=protected-access - self._reconcile() - # copy - new_copy = super().copy() - # Fix invalid id's in _argument_names after copying. - new_list = [] - for idx, child in enumerate(new_copy.children): - my_tuple = (id(child), new_copy._argument_names[idx][1]) - new_list.append(my_tuple) - new_copy._argument_names = new_list - - return new_copy - class UnaryOperation(Operation): ''' @@ -311,23 +136,14 @@ class UnaryOperation(Operation): ''' # Textual description of the node. _children_valid_format = "DataNode" - _text_name = "UnaryOperation" Operator = Enum('Operator', [ # Arithmetic Operators - 'MINUS', 'PLUS', 'SQRT', 'EXP', 'LOG', 'LOG10', + 'MINUS', 'PLUS', # Logical Operators 'NOT', - # Trigonometric Operators - 'COS', 'SIN', 'TAN', 'ACOS', 'ASIN', 'ATAN', - # Other Maths Operators - 'ABS', 'CEIL', 'FLOOR', 'TRANSPOSE', - # Casting Operators - 'REAL', 'INT', 'NINT' ]) - _non_elemental_ops = [] - @staticmethod def _validate_child(position, child): ''' @@ -368,22 +184,7 @@ def create(operator, operand): f"'{type(operator).__name__}'.") unary_op = UnaryOperation(operator) - name = None - if isinstance(operand, tuple): - if not len(operand) == 2: - raise GenerationError( - f"If the argument in the create method of " - f"UnaryOperation class is a tuple, it's length " - f"should be 2, but it is {len(operand)}.") - if not isinstance(operand[0], str): - raise GenerationError( - f"If the argument in the create method of " - f"UnaryOperation class is a tuple, its first " - f"argument should be a str, but found " - f"{type(operand[0]).__name__}.") - name, operand = operand - - unary_op.append_named_arg(name, operand) + unary_op.addchild(operand) return unary_op @@ -400,93 +201,9 @@ class BinaryOperation(Operation): 'EQ', 'NE', 'GT', 'LT', 'GE', 'LE', # Logical Operators 'AND', 'OR', 'EQV', 'NEQV', - # Other Maths Operators - 'SIGN', 'MIN', 'MAX', - # Casting operators - 'REAL', 'INT', 'CAST', - # Array Query Operators - 'SIZE', 'LBOUND', 'UBOUND', - # Matrix and Vector Operators - 'MATMUL', 'DOT_PRODUCT' ]) - _non_elemental_ops = [Operator.MATMUL, Operator.SIZE, - Operator.LBOUND, Operator.UBOUND, - Operator.DOT_PRODUCT] - '''Arithmetic operators: - - .. function:: POW(arg0, arg1) -> type(arg0) - - :returns: `arg0` raised to the power of `arg1`. - - Array query operators: - - .. function:: SIZE(array, index) -> int - - :returns: the size of the `index` dimension of `array`. - - .. function:: LBOUND(array, index) -> int - - :returns: the value of the lower bound of the `index` dimension of \ - `array`. - - .. function:: UBOUND(array, index) -> int - - :returns: the value of the upper bound of the `index` dimension of \ - `array`. - - Casting Operators: - - .. function:: REAL(arg0, precision) - - :returns: `arg0` converted to a floating point number of the \ - specified precision. - - .. function:: INT(arg0, precision) - - :returns: `arg0` converted to an integer number of the specified \ - precision. - - .. function:: CAST(arg0, mold) - - :returns: `arg0` with the same bitwise representation but interpreted \ - with the same type as the specified `mold` argument. - - Matrix and Vector Operators: - - .. function:: MATMUL(array1, array2) -> array - - :returns: the result of performing a matrix multiply with a \ - matrix (`array1`) and a matrix or a vector - (`array2`). - - .. note:: `array1` must be a 2D array. `array2` may be a 2D array - or a 1D array (vector). The size of the second dimension of - `array1` must be the same as the first dimension of - `array1`. If `array2` is 2D then the resultant array will be - 2D with the size of its first dimension being the same as the - first dimension of `array1` and the size of its second - dimension being the same as second dimension of `array2`. If - `array2` is a vector then the resultant array is a vector with - the its size being the size of the first dimension of - `array1`. - - .. note:: The type of data in `array1` and `array2` must be the - same and the resultant data will also have the same - type. Currently only REAL data is supported. - - .. function:: DOT_PRODUCT(vector1, vector2) -> scalar - - :returns: the result of performing a dot product on two equal \ - sized vectors. - - .. note:: The type of data in `vector1` and `vector2` must be the - same and the resultant data will also have the same - type. Currently only REAL data is supported. - - ''' # Textual description of the node. _children_valid_format = "DataNode, DataNode" - _text_name = "BinaryOperation" @staticmethod def _validate_child(position, child): @@ -533,137 +250,12 @@ def create(operator, lhs, rhs): f"operator argument in create method of BinaryOperation class " f"should be a PSyIR BinaryOperation Operator but found " f"'{type(operator).__name__}'.") - for name, arg in [("lhs", lhs), ("rhs", rhs)]: - if isinstance(arg, tuple): - if not len(arg) == 2: - raise GenerationError( - f"If the {name} argument in create method of " - f"BinaryOperation class is a tuple, it's length " - f"should be 2, but it is {len(arg)}.") - if not isinstance(arg[0], str): - raise GenerationError( - f"If the {name} argument in create method of " - f"BinaryOperation class is a tuple, its first " - f"argument should be a str, but found " - f"{type(arg[0]).__name__}.") - - lhs_name = None - if isinstance(lhs, tuple): - lhs_name, lhs = lhs - rhs_name = None - if isinstance(rhs, tuple): - rhs_name, rhs = rhs binary_op = BinaryOperation(operator) - binary_op.append_named_arg(lhs_name, lhs) - binary_op.append_named_arg(rhs_name, rhs) + binary_op.addchild(lhs) + binary_op.addchild(rhs) return binary_op - def reference_accesses(self, var_accesses): - '''Get all reference access information from this node. - If the 'COLLECT-ARRAY-SHAPE-READS' options is set, it - will not report array accesses used as first parameter - in `lbound`, `ubound`, or `size` as 'read' accesses. - - :param var_accesses: VariablesAccessInfo instance that stores the \ - information about variable accesses. - :type var_accesses: \ - :py:class:`psyclone.core.VariablesAccessInfo` - - ''' - if not var_accesses.options("COLLECT-ARRAY-SHAPE-READS") \ - and self.operator in [BinaryOperation.Operator.LBOUND, - BinaryOperation.Operator.UBOUND, - BinaryOperation.Operator.SIZE]: - # If shape accesses are not considered reads, ignore the first - # child (which is always the array being read) - for child in self._children[1:]: - child.reference_accesses(var_accesses) - return - for child in self._children: - child.reference_accesses(var_accesses) - - -class NaryOperation(Operation): - '''Node representing a n-ary operation expression. The n operands are - the stored as the 0 - n-1th children of this node and the type of - the operator is held in an attribute. - - ''' - # Textual description of the node. - _children_valid_format = "[DataNode]+" - _text_name = "NaryOperation" - - Operator = Enum('Operator', [ - # Arithmetic Operators - 'MAX', 'MIN' - ]) - _non_elemental_ops = [] - - @staticmethod - def _validate_child(position, child): - ''' - :param int position: the position to be validated. - :param child: a child to be validated. - :type child: :py:class:`psyclone.psyir.nodes.Node` - - :return: whether the given child and position are valid for this node. - :rtype: bool - - ''' - # pylint: disable=unused-argument - return isinstance(child, DataNode) - - @staticmethod - def create(operator, operands): - '''Create an NaryOperator instance given an operator and a list of - Node (or name and Node tuple) instances. - - :param operator: the operator used in the operation. - :type operator: :py:class:`psyclone.psyir.nodes.NaryOperation.Operator` - :param operands: a list containing PSyIR nodes and/or 2-tuples \ - which contain an argument name and a PSyIR node, that the \ - operator operates on. - :type operands: List[Union[:py:class:`psyclone.psyir.nodes.Node`, \ - Tuple[str, :py:class:`psyclone.psyir.nodes.DataNode`]]] - - :returns: an NaryOperator instance. - :rtype: :py:class:`psyclone.psyir.nodes.NaryOperation` - - :raises GenerationError: if the arguments to the create method \ - are not of the expected type. - - ''' - if not isinstance(operator, Enum) or \ - operator not in NaryOperation.Operator: - raise GenerationError( - f"operator argument in create method of NaryOperation class " - f"should be a PSyIR NaryOperation Operator but found " - f"'{type(operator).__name__}'.") - if not isinstance(operands, list): - raise GenerationError( - f"operands argument in create method of NaryOperation class " - f"should be a list but found '{type(operands).__name__}'.") - - nary_op = NaryOperation(operator) - for operand in operands: - name = None - if isinstance(operand, tuple): - if not len(operand) == 2: - raise GenerationError( - f"If an element of the operands argument in create " - f"method of NaryOperation class is a tuple, it's " - f"length should be 2, but found {len(operand)}.") - if not isinstance(operand[0], str): - raise GenerationError( - f"If an element of the operands argument in create " - f"method of NaryOperation class is a tuple, " - f"its first argument should be a str, but found " - f"{type(operand[0]).__name__}.") - name, operand = operand - nary_op.append_named_arg(name, operand) - return nary_op - # For automatic API documentation generation -__all__ = ["Operation", "UnaryOperation", "BinaryOperation", "NaryOperation"] +__all__ = ["Operation", "UnaryOperation", "BinaryOperation"] diff --git a/src/psyclone/psyir/nodes/ranges.py b/src/psyclone/psyir/nodes/ranges.py index b68e5def10..2814160446 100644 --- a/src/psyclone/psyir/nodes/ranges.py +++ b/src/psyclone/psyir/nodes/ranges.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2021, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -63,20 +63,20 @@ class Range(Node): A common use case is to want to specify all the elements of a given array dimension without knowing the extent of that dimension. In the - PSyIR this is achieved by using the ``LBOUND``, and ``UBOUND`` binary - operators:: + PSyIR this is achieved by using the ``LBOUND``, and ``UBOUND`` + intrinsics:: >>> one = Literal("1", INTEGER_TYPE) >>> # Declare a 1D real array called 'a' with 10 elements >>> symbol = DataSymbol("a", ArrayType(REAL_TYPE, [10])) >>> # Return the lower bound of the first dimension of array 'a' - >>> lbound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol), one.copy()) + >>> lbound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), one.copy()]) >>> # Return the upper bound of the first dimension of array 'a' - >>> ubound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(symbol), one.copy()) + >>> ubound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(symbol), one.copy()]) >>> # Step defaults to 1 so no need to include it when creating range >>> my_range = Range.create(lbound, ubound) >>> # Create an access to all elements in the 1st dimension of array 'a' diff --git a/src/psyclone/psyir/symbols/datasymbol.py b/src/psyclone/psyir/symbols/datasymbol.py index 505e1b93b2..e8a9c7d1ec 100644 --- a/src/psyclone/psyir/symbols/datasymbol.py +++ b/src/psyclone/psyir/symbols/datasymbol.py @@ -213,7 +213,7 @@ def initial_value(self, new_value): ''' # pylint: disable=import-outside-toplevel from psyclone.psyir.nodes import (Node, Literal, Operation, Reference, - CodeBlock) + CodeBlock, IntrinsicCall) from psyclone.psyir.symbols.datatypes import (ScalarType, ArrayType, UnknownType) @@ -234,13 +234,14 @@ def initial_value(self, new_value): if isinstance(new_value, Node): for node in new_value.walk(Node): if not isinstance(node, (Literal, Operation, Reference, - CodeBlock)): + CodeBlock, IntrinsicCall)): raise ValueError( f"Error setting initial value for symbol " f"'{self.name}'. PSyIR static expressions can only" - f" contain PSyIR Literal, Operation, Reference or " - f"CodeBlock nodes but found: {node}") - self._initial_value = new_value + f" contain PSyIR Literal, Operation, Reference," + f" IntrinsicCall or CodeBlock nodes but found: " + f"{node}") + new_initial_value = new_value else: from psyclone.psyir.symbols.datatypes import TYPE_MAP_TO_PYTHON # No need to check that self.datatype has an intrinsic @@ -257,14 +258,25 @@ def initial_value(self, new_value): # In this case we know new_value is a Python boolean as it # has passed the isinstance(new_value, lookup) check. if new_value: - self._initial_value = Literal('true', self.datatype) + new_initial_value = Literal('true', self.datatype) else: - self._initial_value = Literal('false', self.datatype) + new_initial_value = Literal('false', self.datatype) else: # Otherwise we convert the Python intrinsic to a PSyIR # Literal using its string representation. - self._initial_value = Literal(str(new_value), - self.datatype) + new_initial_value = Literal(str(new_value), self.datatype) + + # Add it to a properly formed Assignment parent, this implicitly + # guarantees that the node is not attached anywhere else (and is + # unexpectedly modified) and also makes it similar to any other RHS + # expression, enabling some functionality without special cases. + # Note that the parent dangles on top of the init value, and is not + # referenced directly from anywhere else. + from psyclone.psyir.nodes import Assignment + parent = Assignment() + parent.addchild(Reference(self)) + parent.addchild(new_initial_value) + self._initial_value = new_initial_value else: if self.is_constant and not self.is_import: raise ValueError( @@ -290,10 +302,14 @@ def copy(self): :rtype: :py:class:`psyclone.psyir.symbols.DataSymbol` ''' + if self.initial_value is not None: + new_init_value = self.initial_value.copy() + else: + new_init_value = None return DataSymbol(self.name, self.datatype, visibility=self.visibility, interface=self.interface, is_constant=self.is_constant, - initial_value=self.initial_value) + initial_value=new_init_value) def copy_properties(self, symbol_in): '''Replace all properties in this object with the properties from diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index 3672ff9e79..88e7405d01 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -364,23 +364,42 @@ def __init__(self, datatype, shape): # This import must be placed here to avoid circular dependencies. # pylint: disable=import-outside-toplevel - from psyclone.psyir.nodes import Literal, DataNode + from psyclone.psyir.nodes import Literal, DataNode, Assignment def _node_from_int(var): ''' Helper routine that simply creates a Literal out of an int. If the supplied arg is not an int then it is returned unchanged. :param var: variable for which to create a Literal if necessary. - :type var: int or :py:class:`psyclone.psyir.nodes.DataNode` + :type var: int | :py:class:`psyclone.psyir.nodes.DataNode` | Extent - :returns: a DataNode representing the supplied input. - :rtype: :py:class:`psyclone.psyir.nodes.DataNode` + :returns: the variable with ints converted to DataNodes. + :rtype: :py:class:`psyclone.psyir.nodes.DataNode` | Extent ''' if isinstance(var, int): return Literal(str(var), INTEGER_TYPE) return var + def _dangling_parent(var): + ''' Helper routine that copies and adds a dangling parent + Assignment to a given node, this implicitly guarantees that the + node is not attached anywhere else (and is unexpectedly modified) + and also makes it behave like other nodes (e.g. calls inside an + expression do not have the "call" keyword in Fortran) + + :param var: variable with a dangling parent if necessary. + :type var: int | :py:class:`psyclone.psyir.nodes.DataNode` | Extent + + :returns: the variable with dangling parent when necessary. + :rtype: :py:class:`psyclone.psyir.nodes.DataNode` | Extent + ''' + if isinstance(var, DataNode): + parent = Assignment() + parent.addchild(var.copy()) + return parent.children[0] + return var + if isinstance(datatype, DataType): if isinstance(datatype, StructureType): # TODO #1031 remove this restriction. @@ -412,12 +431,15 @@ def _node_from_int(var): for dim in shape: if isinstance(dim, (DataNode, int)): # The lower bound is 1 by default. - self._shape.append(ArrayType.ArrayBounds(one.copy(), - _node_from_int(dim))) + self._shape.append( + ArrayType.ArrayBounds( + _dangling_parent(one), + _dangling_parent(_node_from_int(dim)))) elif isinstance(dim, tuple): self._shape.append( - ArrayType.ArrayBounds(_node_from_int(dim[0]), - _node_from_int(dim[1]))) + ArrayType.ArrayBounds( + _dangling_parent(_node_from_int(dim[0])), + _dangling_parent(_node_from_int(dim[1])))) else: self._shape.append(dim) diff --git a/src/psyclone/psyir/transformations/arrayrange2loop_trans.py b/src/psyclone/psyir/transformations/arrayrange2loop_trans.py index 61dd8d7ca0..b1aa1a1e30 100644 --- a/src/psyclone/psyir/transformations/arrayrange2loop_trans.py +++ b/src/psyclone/psyir/transformations/arrayrange2loop_trans.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -31,7 +31,7 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -# Author R. W. Ford and N. Nobre, STFC Daresbury Lab +# Author R. W. Ford, N. Nobre and S. Siso, STFC Daresbury Lab # Modified by J. Henrichs, Bureau of Meteorology '''Module providing a transformation from a PSyIR Array Range to a @@ -41,12 +41,10 @@ ''' -from __future__ import absolute_import - from psyclone.core import SymbolicMaths from psyclone.psyGen import Transformation from psyclone.psyir.nodes import Loop, Range, Reference, ArrayReference, \ - Assignment, Operation, BinaryOperation + Assignment, Call, IntrinsicCall from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE from psyclone.psyir.transformations.transformation_error \ import TransformationError @@ -291,20 +289,21 @@ def validate(self, node, options=None): f"least one of its dimensions being a Range, but found None " f"in '{node.lhs}'.") - # If an operator on the rhs only returns an array then we are - # not able to turn the assignment into an explicit loop. At - # the moment we do not capture what an operator returns and - # therefore can not check this. This will be addressed in - # issue #685. For the moment we check with an explicit list - # which happens to only contain a single operator (MATMUL) - # because at this time all other operators in the PSyIR can be - # performed elementwise. - if [operation for operation in node.rhs.walk(Operation) - if operation.operator in [BinaryOperation.Operator.MATMUL]]: + # TODO #2004: Note that the NEMOArrayRange2Loop transforamtion has + # a different implementation that accepts many more statemetns (e.g. + # elemental function calls) but lacks in the use of symbolics. Both + # implementation should be merged (as well as their tests) to one + # combining the advantages of both. + + # Currently we don't accept calls (with the exception of L/UBOUND) + for call in node.rhs.walk(Call): + if isinstance(call, IntrinsicCall) and call.intrinsic in \ + (IntrinsicCall.Intrinsic.LBOUND, + IntrinsicCall.Intrinsic.UBOUND): + continue raise TransformationError( - f"Error in {self.name} transformation. The rhs of the " - f"supplied Assignment node '{node.rhs}' contains the MATMUL " - f"operator which can't be performed elementwise.") + f"Error in {self.name} transformation. The rhs of the supplied" + f" Assignment contains a call '{call.debug_string()}'.") # Find the outermost range for the array on the lhs of the # assignment and save its index. diff --git a/src/psyclone/psyir/transformations/chunk_loop_trans.py b/src/psyclone/psyir/transformations/chunk_loop_trans.py index 85c8cb8663..6cda9a18e1 100644 --- a/src/psyclone/psyir/transformations/chunk_loop_trans.py +++ b/src/psyclone/psyir/transformations/chunk_loop_trans.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2021-2022, Science and Technology Facilities Council. +# Copyright (c) 2021-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -41,7 +41,7 @@ from psyclone.core import VariablesAccessInfo, Signature, AccessType from psyclone.psyir import nodes from psyclone.psyir.nodes import Assignment, BinaryOperation, Reference, \ - Literal, Loop, Schedule, CodeBlock + Literal, Loop, Schedule, CodeBlock, IntrinsicCall from psyclone.psyir.symbols import DataSymbol, ScalarType from psyclone.psyir.transformations.loop_trans import LoopTrans from psyclone.psyir.transformations.transformation_error import \ @@ -255,8 +255,8 @@ def apply(self, node, options=None): BinaryOperation.Operator.SUB, Literal(f"{chunk_size}", node.variable.datatype), Literal("1", node.variable.datatype))) - minop = BinaryOperation.create(BinaryOperation.Operator.MIN, add, - stop.copy()) + minop = IntrinsicCall.create(IntrinsicCall.Intrinsic.MIN, + [add, stop.copy()]) inner_loop_end = Assignment.create(Reference(end_inner_loop), minop) # For negative steps we do: @@ -269,8 +269,8 @@ def apply(self, node, options=None): BinaryOperation.Operator.ADD, Literal(f"{chunk_size}", node.variable.datatype), Literal("1", node.variable.datatype))) - maxop = BinaryOperation.create(BinaryOperation.Operator.MAX, sub, - stop.copy()) + maxop = IntrinsicCall.create(IntrinsicCall.Intrinsic.MAX, + [sub, stop.copy()]) inner_loop_end = Assignment.create(Reference(end_inner_loop), maxop) # chunk_size needs to be negative if we're reducing diff --git a/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py b/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py index 7cb8541d5b..324fdaabd7 100644 --- a/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py +++ b/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py @@ -42,7 +42,6 @@ import copy from psyclone.psyGen import Transformation -from psyclone.psyir.frontend.fortran import FortranReader from psyclone.psyir.nodes import (Routine, Container, ArrayReference, Range, FileContainer, IfBlock, UnaryOperation, CodeBlock, ACCRoutineDirective, Literal, @@ -153,16 +152,15 @@ def apply(self, node, options=None): # associated with the symbol being hoisted. tags_dict = node.symbol_table.get_reverse_tags_dict() - # Fortran reader needed to create Codeblocks in the following loop - freader = FortranReader() - for sym in automatic_arrays: # Keep a copy of the original shape of the array. orig_shape = sym.datatype.shape[:] # Modify the *existing* symbol so that any references to it # remain valid. new_type = copy.copy(sym.datatype) + # pylint: disable=protected-access new_type._shape = len(orig_shape)*[ArrayType.Extent.DEFERRED] + # pylint: enable=protected-access sym.datatype = new_type # Ensure that the promoted symbol is private to the container. sym.visibility = Symbol.Visibility.PRIVATE @@ -183,11 +181,11 @@ def apply(self, node, options=None): for dim in orig_shape] aref = ArrayReference.create(sym, dim_list) - # TODO #1366: we have to use a CodeBlock in order to query whether - # or not the array has been allocated already. - code = f"allocated({sym.name})" - allocated_expr = freader.psyir_from_expression( - code, node.symbol_table) + # Add a conditional expression to avoid repeating the allocation + # if its already done + allocated_expr = IntrinsicCall.create( + IntrinsicCall.Intrinsic.ALLOCATED, + [Reference(sym)]) cond_expr = UnaryOperation.create( UnaryOperation.Operator.NOT, allocated_expr) @@ -198,10 +196,10 @@ def apply(self, node, options=None): if not isinstance(dim.lower, Literal): expr = BinaryOperation.create( BinaryOperation.Operator.NE, - BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(sym), - Literal(str(idx+1), INTEGER_TYPE)), + IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(sym), + ("dim", Literal(str(idx+1), INTEGER_TYPE))]), dim.lower.copy()) # We chain the new check to the already existing cond_expr # which starts with the 'not allocated' condition added @@ -213,10 +211,10 @@ def apply(self, node, options=None): if not isinstance(dim.upper, Literal): expr = BinaryOperation.create( BinaryOperation.Operator.NE, - BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(sym), - Literal(str(idx+1), INTEGER_TYPE)), + IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(sym), + ("dim", Literal(str(idx+1), INTEGER_TYPE))]), dim.upper.copy()) # We chain the new check to the already existing cond_expr # which starts with the 'not allocated' condition added diff --git a/src/psyclone/psyir/transformations/intrinsics/__init__.py b/src/psyclone/psyir/transformations/intrinsics/__init__.py index 2972eb9c4c..c7d5bed383 100644 --- a/src/psyclone/psyir/transformations/intrinsics/__init__.py +++ b/src/psyclone/psyir/transformations/intrinsics/__init__.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -31,10 +31,11 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -# Author R. W. Ford, STFC Daresbury Lab +# Author: R. W. Ford, STFC Daresbury Lab +# Modified: S. Siso, STFC Daresbury Lab '''PSyclone Internal Representation intrinsics transformation module. Contains all transformations that act on PSyIR intrinsic -(operator) functions. +functions. ''' diff --git a/src/psyclone/psyir/transformations/intrinsics/abs2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/abs2code_trans.py index fb04f1da44..0b4a8a28cf 100644 --- a/src/psyclone/psyir/transformations/intrinsics/abs2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/abs2code_trans.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -40,15 +40,14 @@ than the intrinsic. ''' -from __future__ import absolute_import -from psyclone.psyir.transformations.intrinsics.operator2code_trans import \ - Operator2CodeTrans -from psyclone.psyir.nodes import UnaryOperation, BinaryOperation, Assignment, \ - Reference, Literal, IfBlock +from psyclone.psyir.transformations.intrinsics.intrinsic2code_trans import ( + Intrinsic2CodeTrans) +from psyclone.psyir.nodes import ( + BinaryOperation, Assignment, Reference, Literal, IfBlock, IntrinsicCall) from psyclone.psyir.symbols import DataSymbol, REAL_TYPE -class Abs2CodeTrans(Operator2CodeTrans): +class Abs2CodeTrans(Intrinsic2CodeTrans): '''Provides a transformation from a PSyIR ABS Operator node to equivalent code in a PSyIR tree. Validity checks are also performed. @@ -70,10 +69,8 @@ class Abs2CodeTrans(Operator2CodeTrans): ''' def __init__(self): - super(Abs2CodeTrans, self).__init__() - self._operator_name = "ABS" - self._classes = (UnaryOperation,) - self._operators = (UnaryOperation.Operator.ABS,) + super().__init__() + self._intrinsic = IntrinsicCall.Intrinsic.ABS def apply(self, node, options=None): '''Apply the ABS intrinsic conversion transformation to the specified diff --git a/src/psyclone/psyir/transformations/intrinsics/dotproduct2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/dotproduct2code_trans.py index 70b00a9705..fe0c0a0e55 100644 --- a/src/psyclone/psyir/transformations/intrinsics/dotproduct2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/dotproduct2code_trans.py @@ -43,15 +43,14 @@ # pylint: disable=too-many-locals -from __future__ import absolute_import from psyclone.psyir.nodes import BinaryOperation, Assignment, Reference, \ - Loop, Literal, ArrayReference, Range, Routine + Loop, Literal, ArrayReference, Range, Routine, IntrinsicCall from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE, REAL_TYPE, \ ArrayType, ScalarType from psyclone.psyir.transformations.transformation_error \ import TransformationError -from psyclone.psyir.transformations.intrinsics.operator2code_trans import \ - Operator2CodeTrans +from psyclone.psyir.transformations.intrinsics.intrinsic2code_trans import \ + Intrinsic2CodeTrans def _get_array_bound(vector1, vector2): @@ -100,17 +99,17 @@ def _get_array_bound(vector1, vector2): # array so use the LBOUND and UBOUND intrinsics. symbol = vector1.symbol my_dim = symbol.shape[0] - lower_bound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, Reference(symbol), - Literal("1", INTEGER_TYPE)) - upper_bound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, Reference(symbol), - Literal("1", INTEGER_TYPE)) + lower_bound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), ("dim", Literal("1", INTEGER_TYPE))]) + upper_bound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(symbol), ("dim", Literal("1", INTEGER_TYPE))]) step = Literal("1", INTEGER_TYPE) return (lower_bound, upper_bound, step) -class DotProduct2CodeTrans(Operator2CodeTrans): +class DotProduct2CodeTrans(Intrinsic2CodeTrans): '''Provides a transformation from a PSyIR DOT_PRODUCT Operator node to equivalent code in a PSyIR tree. Validity checks are also performed. @@ -135,7 +134,7 @@ class DotProduct2CodeTrans(Operator2CodeTrans): >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.frontend.fortran import FortranReader - >>> from psyclone.psyir.nodes import BinaryOperation + >>> from psyclone.psyir.nodes import IntrinsicCall >>> from psyclone.psyir.transformations import DotProduct2CodeTrans >>> code = ("subroutine dot_product_test(v1,v2)\\n" ... "real,intent(in) :: v1(10), v2(10)\\n" @@ -144,7 +143,7 @@ class DotProduct2CodeTrans(Operator2CodeTrans): ... "end subroutine\\n") >>> psyir = FortranReader().psyir_from_source(code) >>> trans = DotProduct2CodeTrans() - >>> trans.apply(psyir.walk(BinaryOperation)[0]) + >>> trans.apply(psyir.walk(IntrinsicCall)[0]) >>> print(FortranWriter()(psyir)) subroutine dot_product_test(v1, v2) real, dimension(10), intent(in) :: v1 @@ -165,9 +164,7 @@ class DotProduct2CodeTrans(Operator2CodeTrans): ''' def __init__(self): super().__init__() - self._operator_name = "DOTPRODUCT" - self._classes = (BinaryOperation,) - self._operators = (BinaryOperation.Operator.DOT_PRODUCT,) + self._intrinsic = IntrinsicCall.Intrinsic.DOT_PRODUCT def validate(self, node, options=None): '''Perform checks to ensure that it is valid to apply the diff --git a/src/psyclone/psyir/transformations/intrinsics/operator2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/intrinsic2code_trans.py similarity index 66% rename from src/psyclone/psyir/transformations/intrinsics/operator2code_trans.py rename to src/psyclone/psyir/transformations/intrinsics/intrinsic2code_trans.py index 2ca8fef0d8..6873e48975 100644 --- a/src/psyclone/psyir/transformations/intrinsics/operator2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/intrinsic2code_trans.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -33,22 +33,22 @@ # ----------------------------------------------------------------------------- # Author: R. W. Ford, STFC Daresbury Lab # Modified: A. R. Porter and N. Nobre, STFC Daresbury Lab +# Modified: S. Siso, STFC Daresbury Lab '''Module providing an abstract class which provides some generic -functionality required by transformations of PSyIR intrinsic operators -(such as MIN and MAX). +functionality required by transformations of PSyIR intrinsic +(such as MIN and MAX) to code. ''' -from __future__ import absolute_import import abc from psyclone.psyGen import Transformation -from psyclone.psyir.nodes import Assignment +from psyclone.psyir.nodes import Assignment, IntrinsicCall from psyclone.psyir.transformations.transformation_error import \ TransformationError -class Operator2CodeTrans(Transformation, metaclass=abc.ABCMeta): - '''Provides support for transformations from PSyIR intrinsic Operator +class Intrinsic2CodeTrans(Transformation, metaclass=abc.ABCMeta): + '''Provides support for transformations from PSyIR IntrinsicCall nodes to equivalent PSyIR code in a PSyIR tree. Such transformations can be useful when the intrinsic is not supported by a particular backend or if it is more efficient to have @@ -56,53 +56,38 @@ class Operator2CodeTrans(Transformation, metaclass=abc.ABCMeta): ''' def __init__(self): - super(Operator2CodeTrans, self).__init__() - self._operator_name = None - self._classes = None - self._operators = None + super().__init__() + self._intrinsic = None def __str__(self): - return (f"Convert the PSyIR {self._operator_name.upper()} intrinsic " - f"to equivalent PSyIR code.") - - @property - def name(self): - ''' - :returns: the name of the parent transformation as a string. - :rtype:str - - ''' - return f"{self._operator_name.title()}2CodeTrans" + return (f"Convert the PSyIR '{self._intrinsic.name}' " + f"intrinsic to equivalent PSyIR code.") def validate(self, node, options=None): '''Perform various checks to ensure that it is valid to apply an intrinsic transformation to the supplied Node. :param node: the node that is being checked. - :type node: :py:class:`psyclone.psyir.nodes.Operation` + :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` :param options: a dictionary with options for transformations. :type options: Optional[Dict[str, Any]] :raises TransformationError: if the node argument is not the \ expected type. - :raises TransformationError: if the symbol_table argument is not a \ - :py:class:`psyclone.psyir.symbols.SymbolTable`. - :raises TransformationError: if the Operation node does \ + :raises TransformationError: if the IntrinsicCall node does \ not have an Assignment Node as an ancestor. ''' # Check that the node is one of the expected types. - if not isinstance(node, self._classes): + if not isinstance(node, IntrinsicCall): raise TransformationError( - f"Error in {self.name} transformation. The supplied node " - f"argument is not a {self._operator_name} operator, found " - f"'{type(node).__name__}'.") - if node.operator not in self._operators: - oper_names = list(set([oper.name for oper in self._operators])) + f"Error in {self.name} transformation. The supplied node must " + f"be an 'IntrinsicCall', but found '{type(node).__name__}'.") + if node.intrinsic != self._intrinsic: raise TransformationError( - f"Error in {self.name} transformation. The supplied node " - f"operator is invalid, found '{node.operator}', but expected " - f"one of '{oper_names}'.") + f"Error in {self.name} transformation. The supplied " + f"IntrinsicCall must be a '{self._intrinsic.name}' but found: " + f"'{node.intrinsic.name}'.") # Check that there is an Assignment node that is an ancestor # of this Operation. if not node.ancestor(Assignment): @@ -110,8 +95,3 @@ 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.") - - @abc.abstractmethod - def apply(self, node, options=None): - '''Abstract method, see psyclone.psyGen.Transformations apply() for - more details.''' diff --git a/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py index 1472bc4637..b4f577019c 100644 --- a/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py @@ -43,11 +43,11 @@ ''' from psyclone.psyir.nodes import BinaryOperation, Assignment, Reference, \ - Loop, Literal, ArrayReference, Range + Loop, Literal, ArrayReference, Range, IntrinsicCall from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE, REAL_TYPE, \ ArrayType -from psyclone.psyir.transformations.intrinsics.operator2code_trans import \ - Operator2CodeTrans +from psyclone.psyir.transformations.intrinsics.intrinsic2code_trans import \ + Intrinsic2CodeTrans def _create_matrix_ref(matrix_symbol, loop_idx_symbols, other_dims): @@ -106,29 +106,33 @@ def _get_array_bound(array, index): f"Unsupported index type found for array '{array.name}': " f"{err}") from err + dim_index = index + 1 # The Fortran dim argument is 1-indexed if isinstance(my_dim, ArrayType.ArrayBounds): # Use .copy() to ensure we return new nodes. lower_bound = my_dim.lower.copy() if my_dim.upper == ArrayType.Extent.ATTRIBUTE: # Assumed-shape array. - upper_bound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, Reference(array.symbol), - Literal(str(index), INTEGER_TYPE)) + upper_bound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(array.symbol), + ("dim", Literal(str(dim_index), INTEGER_TYPE))]) else: upper_bound = my_dim.upper.copy() else: - lower_bound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, Reference(array.symbol), - Literal(str(index), INTEGER_TYPE)) - upper_bound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, Reference(array.symbol), - Literal(str(index), INTEGER_TYPE)) + lower_bound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(array.symbol), + ("dim", Literal(str(dim_index), INTEGER_TYPE))]) + upper_bound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(array.symbol), + ("dim", Literal(str(dim_index), INTEGER_TYPE))]) step = Literal("1", INTEGER_TYPE) return (lower_bound, upper_bound, step) -class Matmul2CodeTrans(Operator2CodeTrans): +class Matmul2CodeTrans(Intrinsic2CodeTrans): '''Provides a transformation from a PSyIR MATMUL Operator node to equivalent code in a PSyIR tree. Validity checks are also performed. @@ -167,9 +171,7 @@ class Matmul2CodeTrans(Operator2CodeTrans): ''' def __init__(self): super().__init__() - self._operator_name = "MATMUL" - self._classes = (BinaryOperation,) - self._operators = (BinaryOperation.Operator.MATMUL,) + self._intrinsic = IntrinsicCall.Intrinsic.MATMUL def validate(self, node, options=None): '''Perform checks to ensure that it is valid to apply the diff --git a/src/psyclone/psyir/transformations/intrinsics/max2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/max2code_trans.py index 1558607f91..319d138034 100644 --- a/src/psyclone/psyir/transformations/intrinsics/max2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/max2code_trans.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2021, Science and Technology Facilities Council +# Copyright (c) 2021-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,22 +32,21 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: R. W. Ford, STFC Daresbury Laboratory +# Modified: S. Siso, STFC Daresbury Lab -'''Module providing a transformation from a PSyIR MAX operator to -PSyIR code. This could be useful if the MAX operator is not supported +'''Module providing a transformation from a PSyIR MAX intrinsic to +PSyIR code. This could be useful if the MAX intrinsic is not supported by the back-end or if the performance of the inline code is better than the intrinsic. ''' -from __future__ import absolute_import - -from psyclone.psyir.nodes import BinaryOperation, NaryOperation -from psyclone.psyir.transformations.intrinsics.minormax2code_trans import \ - MinOrMax2CodeTrans +from psyclone.psyir.nodes import BinaryOperation, IntrinsicCall +from psyclone.psyir.transformations.intrinsics.minormax2code_trans import ( + MinOrMax2CodeTrans) class Max2CodeTrans(MinOrMax2CodeTrans): - '''Provides a transformation from a PSyIR MAX Operator node to + '''Provides a transformation from a PSyIR MAX Intrinsic node to equivalent code in a PSyIR tree. Validity checks are also performed (by a parent class). @@ -70,8 +69,6 @@ class Max2CodeTrans(MinOrMax2CodeTrans): ''' def __init__(self): - super(Max2CodeTrans, self).__init__() - self._operator_name = "MAX" - self._operators = (BinaryOperation.Operator.MAX, - NaryOperation.Operator.MAX) + super().__init__() + self._intrinsic = IntrinsicCall.Intrinsic.MAX self._compare_operator = BinaryOperation.Operator.GT diff --git a/src/psyclone/psyir/transformations/intrinsics/min2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/min2code_trans.py index f44c084722..8205781dc4 100644 --- a/src/psyclone/psyir/transformations/intrinsics/min2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/min2code_trans.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2021, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -33,22 +33,22 @@ # ----------------------------------------------------------------------------- # Author: R. W. Ford, STFC Daresbury Laboratory # Modified: A. R. Porter, STFC Daresbury Laboratory +# Modified: S. Siso, STFC Daresbury Laboratory -'''Module providing a transformation from a PSyIR MIN operator to -PSyIR code. This could be useful if the MIN operator is not supported +'''Module providing a transformation from a PSyIR MIN intrinsic to +PSyIR code. This could be useful if the MIN intrinsic is not supported by the back-end or if the performance of the inline code is better than the intrinsic. ''' -from __future__ import absolute_import -from psyclone.psyir.nodes import BinaryOperation, NaryOperation +from psyclone.psyir.nodes import BinaryOperation, IntrinsicCall from psyclone.psyir.transformations.intrinsics.minormax2code_trans import \ MinOrMax2CodeTrans class Min2CodeTrans(MinOrMax2CodeTrans): - '''Provides a transformation from a PSyIR MIN Operator node to + '''Provides a transformation from a PSyIR MIN Intrinsic node to equivalent code in a PSyIR tree. Validity checks are also performed (by a parent class). @@ -71,8 +71,6 @@ class Min2CodeTrans(MinOrMax2CodeTrans): ''' def __init__(self): - super(Min2CodeTrans, self).__init__() - self._operator_name = "MIN" - self._operators = (BinaryOperation.Operator.MIN, - NaryOperation.Operator.MIN) + super().__init__() + self._intrinsic = IntrinsicCall.Intrinsic.MIN self._compare_operator = BinaryOperation.Operator.LT diff --git a/src/psyclone/psyir/transformations/intrinsics/minormax2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/minormax2code_trans.py index 3a185bfe32..84a1117f48 100644 --- a/src/psyclone/psyir/transformations/intrinsics/minormax2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/minormax2code_trans.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2021-2022, Science and Technology Facilities Council +# Copyright (c) 2021-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,27 +32,27 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Authors: R. W. Ford and N. Nobre, STFC Daresbury Lab +# Modified: S. Siso, STFC Daresbury Lab '''Module containing a class that provides functionality to transform -a PSyIR MIN or MAX operator to PSyIR code. This could be useful if the -operator is not supported by the back-end or if the performance of the +a PSyIR MIN or MAX intrinsics to PSyIR code. This could be useful if the +intrinsic is not supported by the back-end or if the performance of the inline code is better than the intrinsic. This utility transformation should not be called directly by the user, rather it provides functionality that can be specialised by MIN and MAX-specific transformations. ''' -from __future__ import absolute_import -from psyclone.psyir.nodes import BinaryOperation, NaryOperation, Assignment, \ +from psyclone.psyir.nodes import BinaryOperation, Assignment, \ Reference, IfBlock from psyclone.psyir.symbols import DataSymbol, REAL_TYPE -from psyclone.psyir.transformations.intrinsics.operator2code_trans import \ - Operator2CodeTrans +from psyclone.psyir.transformations.intrinsics.intrinsic2code_trans import \ + Intrinsic2CodeTrans -class MinOrMax2CodeTrans(Operator2CodeTrans): - '''Provides a utility transformation from a PSyIR MIN or MAX Operator +class MinOrMax2CodeTrans(Intrinsic2CodeTrans): + '''Provides a utility transformation from a PSyIR MIN or MAX Intrinsic node to equivalent code in a PSyIR tree. Validity checks are also performed (by the parent class). This utility transformation is not designed to be called directly by the user, rather it should @@ -77,8 +77,7 @@ class MinOrMax2CodeTrans(Operator2CodeTrans): ''' def __init__(self): - super(MinOrMax2CodeTrans, self).__init__() - self._classes = (BinaryOperation, NaryOperation) + super().__init__() self._compare_operator = None def apply(self, node, options=None): @@ -127,19 +126,19 @@ def apply(self, node, options=None): assignment = node.ancestor(Assignment) # Create a temporary result variable. There is an assumption - # here that the Operator returns a PSyIR real type. This + # here that the Intrinsic 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 # appropriate methods to query nodes (see #658). res_var_symbol = symbol_table.new_symbol( - f"res_{self._operator_name.lower()}", + f"res_{self._intrinsic.name.lower()}", symbol_type=DataSymbol, datatype=REAL_TYPE) # Create a temporary variable. Again there is an # assumption here about the datatype - please see previous # comment (associated issue #658). tmp_var_symbol = symbol_table.new_symbol( - f"tmp_{self._operator_name.lower()}", + f"tmp_{self._intrinsic.name.lower()}", symbol_type=DataSymbol, datatype=REAL_TYPE) # Replace operation with a temporary (res_var). diff --git a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py index 255c9ee2b5..cc74a57318 100644 --- a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py @@ -41,8 +41,8 @@ from abc import ABC, abstractmethod from psyclone.psyir.nodes import ( - BinaryOperation, Assignment, Reference, - Literal, Loop, ArrayReference, IfBlock, Range, IntrinsicCall) + Assignment, Reference, Literal, Loop, ArrayReference, IfBlock, Range, + IntrinsicCall) from psyclone.psyir.symbols import ( DataSymbol, INTEGER_TYPE, ScalarType, ArrayType) from psyclone.psyGen import Transformation @@ -134,7 +134,7 @@ def validate(self, node, options=None): # pylint: disable=unidiomatic-typecheck if dim_ref and not ( isinstance(dim_ref, Literal) - or (type(dim_ref) == Reference and + or (type(dim_ref) is Reference and dim_ref.symbol.is_constant and isinstance(dim_ref.symbol.initial_value, Literal))): if isinstance(dim_ref, Reference): @@ -150,7 +150,7 @@ def validate(self, node, options=None): # pylint: disable=unidiomatic-typecheck if not (isinstance(array_ref, ArrayReference) or - (type(array_ref) == Reference)): + (type(array_ref) is Reference)): raise TransformationError( f"{self.name} only supports arrays or plain references for " f"the first argument, but found '{type(array_ref).__name__}'.") @@ -213,7 +213,7 @@ def apply(self, node, options=None): pass elif isinstance(dimension_ref, Literal): dimension_literal = dimension_ref - elif ((type(dimension_ref) == Reference) and + elif ((type(dimension_ref) is Reference) and dimension_ref.symbol.is_constant): dimension_literal = dimension_ref.symbol.initial_value # else exception is handled by the validate method. @@ -235,14 +235,14 @@ def apply(self, node, options=None): if shape == ArrayType.Extent.DEFERRED: allocatable = True # runtime extent using LBOUND and UBOUND required - lbound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(array_ref.symbol), - Literal(str(idx+1), INTEGER_TYPE)) - ubound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(array_ref.symbol), - Literal(str(idx+1), INTEGER_TYPE)) + lbound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(array_ref.symbol), + ("dim", Literal(str(idx+1), INTEGER_TYPE))]) + ubound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(array_ref.symbol), + ("dim", Literal(str(idx+1), INTEGER_TYPE))]) loop_bounds.append((lbound, ubound)) elif isinstance(shape, ArrayType.ArrayBounds): # array extent is defined in the array declaration @@ -348,7 +348,7 @@ def apply(self, node, options=None): # A mask argument has been provided for ref in mask_ref.walk(Reference): # pylint: disable=unidiomatic-typecheck - if ref.name == array_ref.name and type(ref) == Reference: + if ref.name == array_ref.name and type(ref) is Reference: # The array is not indexed so it needs indexing # for the loop nest. shape = [Reference(obj) for obj in loop_iterators] diff --git a/src/psyclone/psyir/transformations/intrinsics/sign2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/sign2code_trans.py index deb2e5c929..94be69f53e 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sign2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sign2code_trans.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,25 +32,24 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: R. W. Ford, STFC Daresbury Laboratory -# Modified: A. R. Porter and N. Nobre, STFC Daresbury Lab +# Modified: A. R. Porter, N. Nobre and S. Siso, STFC Daresbury Lab -'''Module providing a transformation from a PSyIR SIGN operator to -PSyIR code. This could be useful if the SIGN operator is not supported +'''Module providing a transformation from a PSyIR SIGN intrinsic to +PSyIR code. This could be useful if the SIGN intrinsic is not supported by the back-end or if the performance of the inline code is better than the intrinsic. ''' -from __future__ import absolute_import -from psyclone.psyir.transformations.intrinsics.operator2code_trans import \ - Operator2CodeTrans +from psyclone.psyir.transformations.intrinsics.intrinsic2code_trans import ( + Intrinsic2CodeTrans) from psyclone.psyir.transformations import Abs2CodeTrans -from psyclone.psyir.nodes import UnaryOperation, BinaryOperation, Assignment, \ - Reference, Literal, IfBlock +from psyclone.psyir.nodes import ( + BinaryOperation, Assignment, Reference, Literal, IfBlock, IntrinsicCall) from psyclone.psyir.symbols import DataSymbol, REAL_TYPE -class Sign2CodeTrans(Operator2CodeTrans): - '''Provides a transformation from a PSyIR SIGN Operator node to +class Sign2CodeTrans(Intrinsic2CodeTrans): + '''Provides a transformation from a PSyIR SIGN intrinsic node to equivalent code in a PSyIR tree. Validity checks are also performed. @@ -72,15 +71,13 @@ class Sign2CodeTrans(Operator2CodeTrans): ''' def __init__(self): - super(Sign2CodeTrans, self).__init__() - self._operator_name = "SIGN" - self._classes = (BinaryOperation,) - self._operators = (BinaryOperation.Operator.SIGN,) + super().__init__() + self._intrinsic = IntrinsicCall.Intrinsic.SIGN def apply(self, node, options=None): '''Apply the SIGN intrinsic conversion transformation to the specified - node. This node must be a SIGN BinaryOperation. The SIGN - BinaryOperation is converted to equivalent inline code. This + node. This node must be a SIGN IntrinsicCall. The SIGN + IntrinsicCall is converted to equivalent inline code. This is implemented as a PSyIR transform from: .. code-block:: python @@ -107,12 +104,11 @@ def apply(self, node, options=None): ``ABS`` has been replaced with inline code by the NemoAbsTrans transformation. - This transformation requires the operation node to be a - descendent of an assignment and will raise an exception if - this is not the case. + This transformation requires the IntrinsicCall node to be a child of + an assignment and will raise an exception if this is not the case. - :param node: a SIGN BinaryOperation node. - :type node: :py:class:`psyclone.psyir.nodes.BinaryOperation` + :param node: a SIGN IntrinsicCall node. + :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` :param symbol_table: the symbol table. :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable` :param options: a dictionary with options for transformations. @@ -126,7 +122,7 @@ def apply(self, node, options=None): assignment = node.ancestor(Assignment) # Create two temporary variables. There is an assumption here - # that the SIGN Operator returns a PSyIR real type. This might + # 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 @@ -144,7 +140,7 @@ def apply(self, node, options=None): # res_var=ABS(A) lhs = Reference(res_var_symbol) - rhs = UnaryOperation.create(UnaryOperation.Operator.ABS, op1) + rhs = IntrinsicCall.create(IntrinsicCall.Intrinsic.ABS, [op1]) new_assignment = Assignment.create(lhs, rhs) assignment.parent.children.insert(assignment.position, new_assignment) diff --git a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py index fe3ef757fe..527dffba8b 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py @@ -35,7 +35,7 @@ # Modified: S. Siso, STFC Daresbury Lab '''Module providing a transformation from a PSyIR SUM intrinsic to -PSyIR code. This could be useful if the SUM operator is not supported +PSyIR code. This could be useful if the SUM intrinsic is not supported by the back-end, the required parallelisation approach, or if the performance in the inline code is better than the intrinsic. diff --git a/src/psyclone/psyir/transformations/loop_swap_trans.py b/src/psyclone/psyir/transformations/loop_swap_trans.py index c2c072b385..9e3d0149cf 100644 --- a/src/psyclone/psyir/transformations/loop_swap_trans.py +++ b/src/psyclone/psyir/transformations/loop_swap_trans.py @@ -77,7 +77,7 @@ class LoopSwapTrans(LoopTrans): ''' - excluded_node_types = (Call, CodeBlock) + excluded_node_types = (CodeBlock, ) def __str__(self): return "Exchange the order of two nested loops: inner becomes " + \ @@ -131,6 +131,13 @@ def validate(self, node, options=None): f"first two being '{node_outer.loop_body[0]}' and " f"'{node_outer.loop_body[1]}'.") + calls = [call for call in node.walk(Call) if not call.is_pure] + if calls: + raise TransformationError( + f"Nodes of type 'Call' cannot be enclosed by a LoopSwapTrans " + f"unless they can be guaranteed to be pure, but found: " + f"{[call.debug_string() for call in calls]}.") + outer_sched = node_outer.loop_body if outer_sched.symbol_table and \ not outer_sched.symbol_table.is_empty(): diff --git a/src/psyclone/psyir/transformations/reference2arrayrange_trans.py b/src/psyclone/psyir/transformations/reference2arrayrange_trans.py index ad00f77597..e6599f0ca0 100644 --- a/src/psyclone/psyir/transformations/reference2arrayrange_trans.py +++ b/src/psyclone/psyir/transformations/reference2arrayrange_trans.py @@ -44,7 +44,7 @@ from psyclone.errors import LazyString from psyclone.psyGen import Transformation from psyclone.psyir.nodes import (Range, Reference, ArrayReference, Literal, - BinaryOperation, IntrinsicCall) + IntrinsicCall) from psyclone.psyir.symbols import INTEGER_TYPE, ArrayType from psyclone.psyir.transformations.transformation_error \ import TransformationError @@ -115,12 +115,12 @@ def _get_array_bound(symbol, index): # No explicit array bound information could be found so use the # LBOUND and UBOUND intrinsics. - lower_bound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, Reference(symbol), - Literal(str(index+1), INTEGER_TYPE)) - upper_bound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, Reference(symbol), - Literal(str(index+1), INTEGER_TYPE)) + lower_bound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), ("dim", Literal(str(index+1), INTEGER_TYPE))]) + upper_bound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(symbol), ("dim", Literal(str(index+1), INTEGER_TYPE))]) step = Literal("1", INTEGER_TYPE) return (lower_bound, upper_bound, step) @@ -150,14 +150,14 @@ def validate(self, node, options=None): raise TransformationError( f"The supplied node should be a Reference to a symbol " f"that is an array, but '{node.symbol.name}' is not.") - if (isinstance(node.parent, BinaryOperation) and - node.parent.operator in [ - BinaryOperation.Operator.LBOUND, - BinaryOperation.Operator.UBOUND, - BinaryOperation.Operator.SIZE]): + if (isinstance(node.parent, IntrinsicCall) and + node.parent.intrinsic in [ + IntrinsicCall.Intrinsic.LBOUND, + IntrinsicCall.Intrinsic.UBOUND, + IntrinsicCall.Intrinsic.SIZE]): raise TransformationError( "References to arrays within LBOUND, UBOUND or SIZE " - "operators should not be transformed.") + "intrinsics should not be transformed.") if (isinstance(node.parent, IntrinsicCall) and node.parent.routine.name in ["DEALLOCATE"]): raise TransformationError(LazyString( diff --git a/src/psyclone/psyir/transformations/replace_induction_variables_trans.py b/src/psyclone/psyir/transformations/replace_induction_variables_trans.py index 4577ff0984..93e61ee34c 100644 --- a/src/psyclone/psyir/transformations/replace_induction_variables_trans.py +++ b/src/psyclone/psyir/transformations/replace_induction_variables_trans.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2022, Science and Technology Facilities Council. +# Copyright (c) 2022-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,6 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: J. Henrichs, Bureau of Meteorology +# Modified: S. Siso, STFC Daresbury Labs '''Module providing a transformation that removes induction variables from a loop. ''' @@ -148,11 +149,14 @@ def _is_induction_variable(assignment, accesses_in_loop_body): :rtype: bool ''' - # Check if there is an unknown construct or a function call on the - # RHS (note that a function call appears as a code block atm when - # parsed). If so, this variable cannot be replaced (since the function - # value might depend on the number of times it is called). - if any(assignment.rhs.walk((CodeBlock, Call))): + # If there is any unknown construct it can not be guaranteed to be + # invariant + if any(assignment.rhs.walk(CodeBlock)): + return False + + # If there is any function call, unless its pure, we can not guarantee + # the same inputs will always return the same output + if any(not call.is_pure for call in assignment.rhs.walk(Call)): return False # Collect all variables used on the rhs of assignment: diff --git a/src/psyclone/tests/domain/common/transformations/kernel_module_inline_trans_test.py b/src/psyclone/tests/domain/common/transformations/kernel_module_inline_trans_test.py index 4bbf466e1f..e7c75f6f0e 100644 --- a/src/psyclone/tests/domain/common/transformations/kernel_module_inline_trans_test.py +++ b/src/psyclone/tests/domain/common/transformations/kernel_module_inline_trans_test.py @@ -44,9 +44,11 @@ from psyclone.configuration import Config from psyclone.domain.common.transformations import KernelModuleInlineTrans from psyclone.psyGen import CodedKern, Kern -from psyclone.psyir.nodes import Container, Routine, CodeBlock, Call -from psyclone.psyir.symbols import DataSymbol, RoutineSymbol, REAL_TYPE, \ - SymbolError, ContainerSymbol, ImportInterface +from psyclone.psyir.nodes import ( + Container, Routine, CodeBlock, Call, IntrinsicCall) +from psyclone.psyir.symbols import ( + DataSymbol, RoutineSymbol, REAL_TYPE, SymbolError, ContainerSymbol, + ImportInterface) from psyclone.psyir.transformations import TransformationError from psyclone.tests.gocean_build import GOceanBuild from psyclone.tests.lfric_build import LFRicBuild @@ -129,7 +131,7 @@ def test_validate_no_inline_global_var(parser): sched = invoke.schedule kernels = sched.walk(Kern) with pytest.raises(TransformationError) as err: - inline_trans.apply(kernels[0]) + inline_trans.validate(kernels[0]) assert ("'kernel_with_global_code' contains accesses to 'alpha' which is " "declared in the same module scope. Cannot inline such a kernel." in str(err.value)) @@ -146,11 +148,18 @@ def test_validate_no_inline_global_var(parser): kernels[0].get_kernel_schedule().addchild(block) with pytest.raises(TransformationError) as err: - inline_trans.apply(kernels[0]) + inline_trans.validate(kernels[0]) assert ("'kernel_with_global_code' contains accesses to 'alpha' in a " "CodeBlock that is declared in the same module scope. Cannot " "inline such a kernel." in str(err.value)) + # But make sure that an IntrinsicCall routine name is not considered + # a global symbol, as they are implicitly declared everywhere + kernels[0].get_kernel_schedule().pop_all_children() + kernels[0].get_kernel_schedule().addchild( + IntrinsicCall.create(IntrinsicCall.Intrinsic.DATE_AND_TIME, [])) + inline_trans.validate(kernels[0]) + def test_validate_name_clashes(): ''' Test that if the module-inline transformation finds the kernel name diff --git a/src/psyclone/tests/domain/lfric/builtins/setval_random_test.py b/src/psyclone/tests/domain/lfric/builtins/setval_random_test.py index cc93116e61..57ae56cd1a 100644 --- a/src/psyclone/tests/domain/lfric/builtins/setval_random_test.py +++ b/src/psyclone/tests/domain/lfric/builtins/setval_random_test.py @@ -44,7 +44,7 @@ from psyclone.domain.lfric.lfric_builtins import LFRicSetvalRandomKern from psyclone.parse.algorithm import parse from psyclone.psyGen import PSyFactory -from psyclone.psyir.nodes import Call +from psyclone.psyir.nodes import IntrinsicCall from psyclone.tests.lfric_build import LFRicBuild @@ -104,5 +104,5 @@ def test_setval_random_lowering(): parent = kern.parent lowered = kern.lower_to_language_level() assert parent.children[0] is lowered - assert isinstance(parent.children[0], Call) - assert parent.children[0].routine.name == "random_number" + assert isinstance(parent.children[0], IntrinsicCall) + assert parent.children[0].routine.name == "RANDOM_NUMBER" diff --git a/src/psyclone/tests/domain/lfric/lfric_types_test.py b/src/psyclone/tests/domain/lfric/lfric_types_test.py index 1f8cb88e4f..e24d6b2d29 100644 --- a/src/psyclone/tests/domain/lfric/lfric_types_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_types_test.py @@ -327,8 +327,8 @@ def test_arrays(data_type_name, symbol_name, scalar_type_name, if isinstance(dim.upper, Literal): assert dim.upper.value == str(dims[idx]) elif isinstance(dim.upper, Reference): - assert dim.upper is dims[idx] - assert dim.upper.symbol is dims[idx].symbol + assert dim.upper == dims[idx] + assert dim.upper.symbol == dims[idx].symbol else: assert False, "unexpected type of dimension found" # Wrong number of dims diff --git a/src/psyclone/tests/domain/nemo/transformations/nemo_allarrayrange2loop_trans_test.py b/src/psyclone/tests/domain/nemo/transformations/nemo_allarrayrange2loop_trans_test.py index 6cd854906e..4f2d9de024 100644 --- a/src/psyclone/tests/domain/nemo/transformations/nemo_allarrayrange2loop_trans_test.py +++ b/src/psyclone/tests/domain/nemo/transformations/nemo_allarrayrange2loop_trans_test.py @@ -75,8 +75,8 @@ def test_transform_apply_mixed_implicit_do(): result = writer(schedule) expected = ( " do jk = 1, jpk, 1\n" - " do idx = LBOUND(umask, 2), UBOUND(umask, 2), 1\n" - " do idx_1 = LBOUND(umask, 1), UBOUND(umask, 1), 1\n" + " do idx = LBOUND(umask, dim=2), UBOUND(umask, dim=2), 1\n" + " do idx_1 = LBOUND(umask, dim=1), UBOUND(umask, dim=1), 1\n" " umask(idx_1,idx,jk) = vmask(idx_1,idx,jk) + 1.0\n" " enddo\n" " enddo\n" @@ -97,9 +97,9 @@ def test_apply_multi_assignments(): writer = FortranWriter() result = writer(schedule) expected = ( - " do idx = LBOUND(zftv, 3), UBOUND(zftv, 3), 1\n" - " do idx_1 = LBOUND(zftv, 2), UBOUND(zftv, 2), 1\n" - " do idx_2 = LBOUND(zftv, 1), UBOUND(zftv, 1), 1\n" + " do idx = LBOUND(zftv, dim=3), UBOUND(zftv, dim=3), 1\n" + " do idx_1 = LBOUND(zftv, dim=2), UBOUND(zftv, dim=2), 1\n" + " do idx_2 = LBOUND(zftv, dim=1), UBOUND(zftv, dim=1), 1\n" " zftv(idx_2,idx_1,idx) = 0.0d0\n" " enddo\n" " enddo\n" @@ -108,13 +108,13 @@ def test_apply_multi_assignments(): " call dia_ptr_hst(jn, 'ldf', -zftv(:,:,:))\n" " end if\n" " call dia_ptr_hst(jn, 'ldf', -zftv(:,:,:))\n" - " do idx_3 = LBOUND(zftu, 2), UBOUND(zftu, 2), 1\n" - " do idx_4 = LBOUND(zftu, 1), UBOUND(zftu, 1), 1\n" + " do idx_3 = LBOUND(zftu, dim=2), UBOUND(zftu, dim=2), 1\n" + " do idx_4 = LBOUND(zftu, dim=1), UBOUND(zftu, dim=1), 1\n" " zftu(idx_4,idx_3,1) = 1.0d0\n" " enddo\n" " enddo\n" - " do idx_5 = LBOUND(tmask, 2), UBOUND(tmask, 2), 1\n" - " do idx_6 = LBOUND(tmask, 1), UBOUND(tmask, 1), 1\n" + " do idx_5 = LBOUND(tmask, dim=2), UBOUND(tmask, dim=2), 1\n" + " do idx_6 = LBOUND(tmask, dim=1), UBOUND(tmask, dim=1), 1\n" " tmask(idx_6,idx_5) = jpi\n" " enddo\n" " enddo\n") diff --git a/src/psyclone/tests/domain/nemo/transformations/nemo_arrayrange2loop_trans_test.py b/src/psyclone/tests/domain/nemo/transformations/nemo_arrayrange2loop_trans_test.py index 1967e65bf1..119f85ea9a 100644 --- a/src/psyclone/tests/domain/nemo/transformations/nemo_arrayrange2loop_trans_test.py +++ b/src/psyclone/tests/domain/nemo/transformations/nemo_arrayrange2loop_trans_test.py @@ -84,9 +84,9 @@ def test_apply_bounds(tmpdir): writer = FortranWriter() result = writer(schedule) assert ( - " do idx = LBOUND(umask, 3), UBOUND(umask, 3), 1\n" + " do idx = LBOUND(umask, dim=3), UBOUND(umask, dim=3), 1\n" " do idx_1 = 2, 4, 1\n" - " do idx_2 = LBOUND(umask, 1), UBOUND(umask, 1), 1\n" + " do idx_2 = LBOUND(umask, dim=1), UBOUND(umask, dim=1), 1\n" " umask(idx_2,idx_1,idx) = 0.0d0\n" " enddo\n" " enddo\n" @@ -112,9 +112,9 @@ def test_apply_different_dims(tmpdir): writer = FortranWriter() result = writer(schedule) assert ( - " do idx = LBOUND(umask, 5), UBOUND(umask, 5), 1\n" - " do idx_1 = LBOUND(umask, 3), UBOUND(umask, 3), 1\n" - " do idx_2 = LBOUND(umask, 1), UBOUND(umask, 1), 1\n" + " do idx = LBOUND(umask, dim=5), UBOUND(umask, dim=5), 1\n" + " do idx_1 = LBOUND(umask, dim=3), UBOUND(umask, dim=3), 1\n" + " do idx_2 = LBOUND(umask, dim=1), UBOUND(umask, dim=1), 1\n" " umask(idx_2,jpj,idx_1,ndim,idx) = vmask(jpi,idx_2,idx_1,idx," "ndim) + 1.0\n" " enddo\n" @@ -140,7 +140,7 @@ def test_apply_var_name(tmpdir): writer = FortranWriter() result = writer(schedule) assert ( - " do idx_1 = LBOUND(umask, 5), UBOUND(umask, 5), 1\n" + " do idx_1 = LBOUND(umask, dim=5), UBOUND(umask, dim=5), 1\n" " umask(:,:,:,:,idx_1) = vmask(:,:,:,:,idx_1) + 1.0\n" " enddo" in result) assert Compile(tmpdir).string_compiles(result) @@ -166,9 +166,9 @@ def test_apply_structure_of_arrays(fortran_reader, fortran_writer): trans.apply(array_ref.children[0]) result = fortran_writer(psyir) assert ( - " do idx = LBOUND(umask, 3), UBOUND(umask, 3), 1\n" - " do idx_1 = LBOUND(umask, 2), UBOUND(umask, 2), 1\n" - " do idx_2 = LBOUND(umask, 1), UBOUND(umask, 1), 1\n" + " do idx = LBOUND(umask, dim=3), UBOUND(umask, dim=3), 1\n" + " do idx_1 = LBOUND(umask, dim=2), UBOUND(umask, dim=2), 1\n" + " do idx_2 = LBOUND(umask, dim=1), UBOUND(umask, dim=1), 1\n" " umask(idx_2,idx_1,idx) = mystruct%field(idx_2,idx_1,idx) " "+ mystruct%field2%field(idx_2,idx_1,idx)\n" " enddo\n" @@ -188,12 +188,12 @@ def test_apply_structure_of_arrays(fortran_reader, fortran_writer): trans.apply(array_ref.member.member.children[0]) result = fortran_writer(psyir) assert ( - " do idx = LBOUND(mystruct%field2%field, 3), " - "UBOUND(mystruct%field2%field, 3), 1\n" - " do idx_1 = LBOUND(mystruct%field2%field, 2), " - "UBOUND(mystruct%field2%field, 2), 1\n" - " do idx_2 = LBOUND(mystruct%field2%field, 1), " - "UBOUND(mystruct%field2%field, 1), 1\n" + " do idx = LBOUND(mystruct%field2%field, dim=3), " + "UBOUND(mystruct%field2%field, dim=3), 1\n" + " do idx_1 = LBOUND(mystruct%field2%field, dim=2), " + "UBOUND(mystruct%field2%field, dim=2), 1\n" + " do idx_2 = LBOUND(mystruct%field2%field, dim=1), " + "UBOUND(mystruct%field2%field, dim=1), 1\n" " mystruct%field2%field(idx_2,idx_1,idx) = 0.0d0\n" " enddo\n" " enddo\n" @@ -212,12 +212,12 @@ def test_apply_structure_of_arrays(fortran_reader, fortran_writer): trans.apply(array_ref.member.children[1]) result = fortran_writer(psyir) assert ( - " do idx = LBOUND(mystruct%field3, 3), " - "UBOUND(mystruct%field3, 3), 1\n" - " do idx_1 = LBOUND(mystruct%field3, 2), " - "UBOUND(mystruct%field3, 2), 1\n" - " do idx_2 = LBOUND(mystruct%field3, 1), " - "UBOUND(mystruct%field3, 1), 1\n" + " do idx = LBOUND(mystruct%field3, dim=3), " + "UBOUND(mystruct%field3, dim=3), 1\n" + " do idx_1 = LBOUND(mystruct%field3, dim=2), " + "UBOUND(mystruct%field3, dim=2), 1\n" + " do idx_2 = LBOUND(mystruct%field3, dim=1), " + "UBOUND(mystruct%field3, dim=1), 1\n" " mystruct%field3(idx_2,idx_1,idx)%field4 = 0.0d0\n" " enddo\n" " enddo\n" @@ -236,12 +236,12 @@ def test_apply_structure_of_arrays(fortran_reader, fortran_writer): trans.apply(array_ref.member.children[1]) result = fortran_writer(psyir) assert ( - " do idx = LBOUND(mystruct%field3, 3), " - "UBOUND(mystruct%field3, 3), 1\n" - " do idx_1 = LBOUND(mystruct%field3, 2), " - "UBOUND(mystruct%field3, 2), 1\n" - " do idx_2 = LBOUND(mystruct%field3, 1), " - "UBOUND(mystruct%field3, 1), 1\n" + " do idx = LBOUND(mystruct%field3, dim=3), " + "UBOUND(mystruct%field3, dim=3), 1\n" + " do idx_1 = LBOUND(mystruct%field3, dim=2), " + "UBOUND(mystruct%field3, dim=2), 1\n" + " do idx_2 = LBOUND(mystruct%field3, dim=1), " + "UBOUND(mystruct%field3, dim=1), 1\n" " mystruct%field3(idx_2,idx_1,idx)%field4 = " "mystruct%field2%field(idx_2,idx_1,idx)\n" " enddo\n" @@ -270,12 +270,12 @@ def test_apply_structure_of_arrays_multiple_arrays(fortran_reader, trans.apply(array_ref.member.member.children[0]) result = fortran_writer(psyir) assert ( - " do idx = LBOUND(mystruct%field2(4,3)%field, 3), " - "UBOUND(mystruct%field2(4,3)%field, 3), 1\n" - " do idx_1 = LBOUND(mystruct%field2(4,3)%field, 2), " - "UBOUND(mystruct%field2(4,3)%field, 2), 1\n" - " do idx_2 = LBOUND(mystruct%field2(4,3)%field, 1), " - "UBOUND(mystruct%field2(4,3)%field, 1), 1\n" + " do idx = LBOUND(mystruct%field2(4,3)%field, dim=3), " + "UBOUND(mystruct%field2(4,3)%field, dim=3), 1\n" + " do idx_1 = LBOUND(mystruct%field2(4,3)%field, dim=2), " + "UBOUND(mystruct%field2(4,3)%field, dim=2), 1\n" + " do idx_2 = LBOUND(mystruct%field2(4,3)%field, dim=1), " + "UBOUND(mystruct%field2(4,3)%field, dim=1), 1\n" " mystruct%field2(4,3)%field(idx_2,idx_1,idx) = " "mystruct%field2(5,8)%field(idx_2,idx_1,idx)\n" " enddo\n" @@ -571,8 +571,8 @@ def test_validate_array_non_elemental_operator(): trans.apply(array_ref.children[0]) assert ( "Error in NemoArrayRange2LoopTrans transformation. This " - "transformation does not support non-elemental Operations on the rhs " - "of the associated Assignment node, but found 'MATMUL' in:" + "transformation does not support non-elemental IntrinsicCalls on the " + "rhs of the associated Assignment node, but found 'MATMUL' in:" in str(info.value)) diff --git a/src/psyclone/tests/domain/nemo/transformations/nemo_outerarrayrange2loop_trans_test.py b/src/psyclone/tests/domain/nemo/transformations/nemo_outerarrayrange2loop_trans_test.py index 447843b8c9..a7fccb185e 100644 --- a/src/psyclone/tests/domain/nemo/transformations/nemo_outerarrayrange2loop_trans_test.py +++ b/src/psyclone/tests/domain/nemo/transformations/nemo_outerarrayrange2loop_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -36,8 +36,6 @@ '''Module containing tests for the NemoOuterArrayRange2LoopTrans transformation.''' -from __future__ import absolute_import - import pytest from fparser.common.readfortran import FortranStringReader @@ -79,7 +77,7 @@ def test_transform_apply_mixed_implicit_do(tmpdir): result = writer(schedule) expected = ( " do jk = 1, jpk, 1\n" - " do idx = LBOUND(umask, 2), UBOUND(umask, 2), 1\n" + " do idx = LBOUND(umask, dim=2), UBOUND(umask, dim=2), 1\n" " umask(:,idx,jk) = vmask(:,idx,jk) + 1.0\n" " enddo\n" " enddo") @@ -88,8 +86,8 @@ def test_transform_apply_mixed_implicit_do(tmpdir): result = writer(schedule) expected = ( " do jk = 1, jpk, 1\n" - " do idx = LBOUND(umask, 2), UBOUND(umask, 2), 1\n" - " do idx_1 = LBOUND(umask, 1), UBOUND(umask, 1), 1\n" + " do idx = LBOUND(umask, dim=2), UBOUND(umask, dim=2), 1\n" + " do idx_1 = LBOUND(umask, dim=1), UBOUND(umask, dim=1), 1\n" " umask(idx_1,idx,jk) = vmask(idx_1,idx,jk) + 1.0\n" " enddo\n" " enddo\n" diff --git a/src/psyclone/tests/psyad/tl2ad_test.py b/src/psyclone/tests/psyad/tl2ad_test.py index 2986fb3db7..70a37151c9 100644 --- a/src/psyclone/tests/psyad/tl2ad_test.py +++ b/src/psyclone/tests/psyad/tl2ad_test.py @@ -31,7 +31,7 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -# Authors R. W. Ford and A. R. Porter, STFC Daresbury Lab +# Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab '''A module to perform pytest tests on the code in the tl2ad.py file within the psyad directory. @@ -49,7 +49,7 @@ _get_active_variables_datatype, _add_precision_symbol) from psyclone.psyir.nodes import ( Container, FileContainer, Return, Routine, Assignment, BinaryOperation, - UnaryOperation, Literal) + IntrinsicCall, Literal) from psyclone.psyir.symbols import ( DataSymbol, SymbolTable, REAL_DOUBLE_TYPE, INTEGER_TYPE, REAL_TYPE, ArrayType, RoutineSymbol, ImportInterface, ScalarType, ContainerSymbol, @@ -932,13 +932,13 @@ def test_generate_harness_kernel_arg_invalid_shape(fortran_reader): # Break one of the bounds in the Range inside the argument shape by making # it into a UnaryOperation node. fld_arg.datatype._shape[0] = ArrayType.ArrayBounds( - lower=UnaryOperation.create(UnaryOperation.Operator.NINT, - Literal("1", INTEGER_TYPE)), + lower=IntrinsicCall.create(IntrinsicCall.Intrinsic.NINT, + [Literal("1", INTEGER_TYPE)]), upper=fld_arg.datatype._shape[0].upper) with pytest.raises(NotImplementedError) as err: generate_adjoint_test(tl_psyir, ad_psyir, ["field1"]) assert ("Found argument 'field1' to kernel 'kernel' which has an array " - "bound specified by a 'UnaryOperation' node. Only Literals or " + "bound specified by a 'IntrinsicCall' node. Only Literals or " "References are supported" in str(err.value)) # Break the argument shape. fld_arg.datatype._shape = [1] + fld_arg.datatype._shape[1:] diff --git a/src/psyclone/tests/psyad/transformations/test_assignment_trans.py b/src/psyclone/tests/psyad/transformations/test_assignment_trans.py index a9dd5546a2..36f259163e 100644 --- a/src/psyclone/tests/psyad/transformations/test_assignment_trans.py +++ b/src/psyclone/tests/psyad/transformations/test_assignment_trans.py @@ -30,12 +30,11 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -# Authors: R. W. Ford, A. R. Porter and N. Nobre, STFC Daresbury Lab +# Authors: R. W. Ford, A. R. Porter, N. Nobre and S. Siso, STFC Daresbury Lab # Modified by J. Henrichs, Bureau of Meteorology -# + '''Module to test the psyad assignment transformation.''' -from __future__ import absolute_import import pytest from psyclone.psyad.transformations import AssignmentTrans, TangentLinearError @@ -43,7 +42,7 @@ from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.frontend.fortran import FortranReader from psyclone.psyir.nodes import BinaryOperation, Reference, Assignment, \ - Literal, UnaryOperation, ArrayReference, Range + Literal, UnaryOperation, ArrayReference, Range, IntrinsicCall from psyclone.psyir.symbols import DataSymbol, REAL_TYPE, INTEGER_TYPE, \ ScalarType, ArrayType from psyclone.psyir.transformations import TransformationError @@ -1277,8 +1276,8 @@ def test_validate_unaryop(): ''' lhs_symbol = DataSymbol("a", REAL_TYPE) rhs_symbol = DataSymbol("b", REAL_TYPE) - sqrt = UnaryOperation.create( - UnaryOperation.Operator.SQRT, Reference(rhs_symbol)) + sqrt = IntrinsicCall.create( + IntrinsicCall.Intrinsic.SQRT, [Reference(rhs_symbol)]) assignment = Assignment.create(Reference(lhs_symbol), sqrt) trans = AssignmentTrans(active_variables=[ lhs_symbol, rhs_symbol]) diff --git a/src/psyclone/tests/psyad/transformations/test_preprocess.py b/src/psyclone/tests/psyad/transformations/test_preprocess.py index 8ed748e0df..2a6bfed6dd 100644 --- a/src/psyclone/tests/psyad/transformations/test_preprocess.py +++ b/src/psyclone/tests/psyad/transformations/test_preprocess.py @@ -31,7 +31,7 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -# Authors: R. W. Ford and A. R. Porter, STFC Daresbury Lab +# Authors: R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab # Modified: J. Henrichs, Bureau of Meteorology '''A module to perform pytest tests on the code in the preprocess.py @@ -207,8 +207,8 @@ def test_preprocess_arrayrange2loop(tmpdir, fortran_reader, fortran_writer): " real, dimension(10,10,10) :: f\n" " integer :: idx\n" " integer :: idx_1\n\n" - " do idx = LBOUND(a, 3), UBOUND(a, 3), 1\n" - " do idx_1 = LBOUND(a, 1), UBOUND(a, 1), 1\n" + " do idx = LBOUND(a, dim=3), UBOUND(a, dim=3), 1\n" + " do idx_1 = LBOUND(a, dim=1), UBOUND(a, dim=1), 1\n" " a(idx_1,1,idx) = b(idx_1,1,idx) * c(idx_1,1,idx)\n" " enddo\n" " enddo\n" diff --git a/src/psyclone/tests/psyir/backend/c_test.py b/src/psyclone/tests/psyir/backend/c_test.py index fe2acf12ec..4e10279661 100644 --- a/src/psyclone/tests/psyir/backend/c_test.py +++ b/src/psyclone/tests/psyir/backend/c_test.py @@ -44,7 +44,7 @@ from psyclone.psyir.nodes import ArrayReference, Assignment, BinaryOperation, \ CodeBlock, IfBlock, Literal, Node, Reference, Return, Schedule, \ UnaryOperation, Loop, OMPTaskloopDirective, OMPMasterDirective, \ - OMPParallelDirective + OMPParallelDirective, IntrinsicCall from psyclone.psyir.symbols import ArgumentInterface, ArrayType, \ BOOLEAN_TYPE, CHARACTER_TYPE, DataSymbol, INTEGER_TYPE, REAL_TYPE @@ -287,16 +287,7 @@ def test_cw_unaryoperator(): # Test all supported Operators test_list = ((UnaryOperation.Operator.PLUS, '(+a)'), (UnaryOperation.Operator.MINUS, '(-a)'), - (UnaryOperation.Operator.SQRT, 'sqrt(a)'), - (UnaryOperation.Operator.NOT, '(!a)'), - (UnaryOperation.Operator.COS, 'cos(a)'), - (UnaryOperation.Operator.SIN, 'sin(a)'), - (UnaryOperation.Operator.TAN, 'tan(a)'), - (UnaryOperation.Operator.ACOS, 'acos(a)'), - (UnaryOperation.Operator.ASIN, 'asin(a)'), - (UnaryOperation.Operator.ATAN, 'atan(a)'), - (UnaryOperation.Operator.ABS, 'abs(a)'), - (UnaryOperation.Operator.REAL, '(float)a')) + (UnaryOperation.Operator.NOT, '(!a)')) for operator, expected in test_list: unary_operation._operator = operator @@ -339,7 +330,6 @@ def test_cw_binaryoperator(): (BinaryOperation.Operator.SUB, '(a - b)'), (BinaryOperation.Operator.MUL, '(a * b)'), (BinaryOperation.Operator.DIV, '(a / b)'), - (BinaryOperation.Operator.REM, '(a % b)'), (BinaryOperation.Operator.POW, 'pow(a, b)'), (BinaryOperation.Operator.EQ, '(a == b)'), (BinaryOperation.Operator.NE, '(a != b)'), @@ -347,9 +337,8 @@ def test_cw_binaryoperator(): (BinaryOperation.Operator.GE, '(a >= b)'), (BinaryOperation.Operator.LT, '(a < b)'), (BinaryOperation.Operator.LE, '(a <= b)'), - (BinaryOperation.Operator.AND, '(a && b)'), (BinaryOperation.Operator.OR, '(a || b)'), - (BinaryOperation.Operator.SIGN, 'copysign(a, b)')) + (BinaryOperation.Operator.AND, '(a && b)')) for operator, expected in test_list: binary_operation._operator = operator @@ -367,6 +356,59 @@ def __init__(self): assert "' operator." in str(err.value) +def test_cw_intrinsiccall(): + '''Check the CWriter class intrinsiccall method correctly prints out + the C representation of any given Intrinsic. + + ''' + cwriter = CWriter() + + # Test all supported Intrinsics with 1 argument + test_list = ((IntrinsicCall.Intrinsic.SQRT, 'sqrt(a)'), + (IntrinsicCall.Intrinsic.COS, 'cos(a)'), + (IntrinsicCall.Intrinsic.SIN, 'sin(a)'), + (IntrinsicCall.Intrinsic.TAN, 'tan(a)'), + (IntrinsicCall.Intrinsic.ACOS, 'acos(a)'), + (IntrinsicCall.Intrinsic.ASIN, 'asin(a)'), + (IntrinsicCall.Intrinsic.ATAN, 'atan(a)'), + (IntrinsicCall.Intrinsic.ABS, 'abs(a)'), + (IntrinsicCall.Intrinsic.REAL, '(float)a')) + ref1 = Reference(DataSymbol("a", REAL_TYPE)) + icall = IntrinsicCall.create(IntrinsicCall.Intrinsic.SQRT, [ref1]) + for intrinsic, expected in test_list: + icall._intrinsic = intrinsic + assert cwriter(icall) == expected + + # Check that operator-style formatting with a number of children different + # than 2 produces an error + with pytest.raises(VisitorError) as err: + icall._intrinsic = IntrinsicCall.Intrinsic.MOD + _ = cwriter(icall) + assert ("The C Writer binary_operator formatter for IntrinsicCall only " + "supports intrinsics with 2 children, but found '%' with '1' " + "children." in str(err.value)) + + # Test all supported Intrinsics with 2 arguments + test_list = ( + (IntrinsicCall.Intrinsic.MOD, '(a % b)'), + (IntrinsicCall.Intrinsic.SIGN, 'copysign(a, b)'), + ) + ref1 = Reference(DataSymbol("a", REAL_TYPE)) + ref2 = Reference(DataSymbol("b", REAL_TYPE)) + icall = IntrinsicCall.create(IntrinsicCall.Intrinsic.MOD, [ref1, ref2]) + for intrinsic, expected in test_list: + icall._intrinsic = intrinsic + assert cwriter(icall) == expected + + # Check that casts with more than one children produce an error + with pytest.raises(VisitorError) as err: + icall._intrinsic = IntrinsicCall.Intrinsic.REAL + _ = cwriter(icall) + assert ("The C Writer IntrinsicCall cast-style formatter only supports " + "intrinsics with 1 child, but found 'float' with '2' children." + in str(err.value)) + + def test_cw_loop(fortran_reader): '''Tests writing out a Loop node in C. It parses Fortran code and outputs it as C. Note that this is atm a literal translation, @@ -398,19 +440,20 @@ def test_cw_loop(fortran_reader): assert correct in result -def test_cw_size(): - ''' Check the CWriter class SIZE method raises the expected error since +def test_cw_unsupported_intrinsiccall(): + ''' Check the CWriter class SIZE intrinsic raises the expected error since there is no C equivalent. ''' cwriter = CWriter() arr = ArrayReference(DataSymbol('a', INTEGER_TYPE)) lit = Literal('1', INTEGER_TYPE) - size = BinaryOperation.create(BinaryOperation.Operator.SIZE, arr, lit) + size = IntrinsicCall.create(IntrinsicCall.Intrinsic.SIZE, + [arr, ("dim", lit)]) lhs = Reference(DataSymbol('length', INTEGER_TYPE)) assignment = Assignment.create(lhs, size) with pytest.raises(VisitorError) as excinfo: cwriter(assignment) - assert ("C backend does not support the 'Operator.SIZE' operator" + assert ("The C backend does not support the 'SIZE' intrinsic." in str(excinfo.value)) diff --git a/src/psyclone/tests/psyir/backend/fortran_test.py b/src/psyclone/tests/psyir/backend/fortran_test.py index abb283f70a..cadd1bc74a 100644 --- a/src/psyclone/tests/psyir/backend/fortran_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_test.py @@ -43,10 +43,10 @@ from fparser.common.readfortran import FortranStringReader from psyclone.psyir.backend.visitor import VisitorError from psyclone.psyir.backend.fortran import gen_intent, gen_datatype, \ - FortranWriter, precedence, _validate_named_args + FortranWriter, precedence from psyclone.psyir.nodes import ( Assignment, Node, CodeBlock, Container, Literal, UnaryOperation, - BinaryOperation, NaryOperation, Reference, Call, KernelSchedule, + BinaryOperation, Reference, Call, KernelSchedule, ArrayReference, ArrayOfStructuresReference, Range, StructureReference, Schedule, Routine, Return, FileContainer, IfBlock, OMPTaskloopDirective, OMPMasterDirective, OMPParallelDirective, Loop, OMPNumTasksClause, @@ -490,9 +490,8 @@ def test_reverse_map_duplicates(): @pytest.mark.parametrize("operator,result", - [(UnaryOperation.Operator.SIN, "SIN"), - (BinaryOperation.Operator.MIN, "MIN"), - (NaryOperation.Operator.MAX, "MAX")]) + [(UnaryOperation.Operator.MINUS, "-"), + (BinaryOperation.Operator.MUL, "*")]) def test_get_operator(operator, result): '''Check that the get_operator function returns the expected values when provided with valid unary, binary and nary operators. @@ -510,18 +509,6 @@ def test_get_operator_error(): _ = FortranWriter().get_operator(None) -def test_is_fortran_intrinsic(): - '''Check that the is_fortran_intrinsic function returns true if the - supplied operator is a fortran intrinsic and false otherwise. - - ''' - - writer = FortranWriter() - assert writer.is_intrinsic("SIN") - assert not writer.is_intrinsic("+") - assert not writer.is_intrinsic(None) - - def test_precedence(): '''Check that the precedence function returns the expected relative precedence values. @@ -543,28 +530,30 @@ def test_precedence_error(): _ = precedence('invalid') -def test_validate_named_args(): - '''Check that the _validate_named_args utility function works as - expected +def test_gen_arguments_validation(fortran_writer): + '''Check that the _gen_arguments validation function works as + expected. ''' # type error with pytest.raises(TypeError) as info: - _validate_named_args(None) - assert ("The _validate_named_args utility function expects either a " - "Call or Operation node, but found 'NoneType'." in str(info.value)) + fortran_writer._gen_arguments(None) + assert ("The _gen_arguments utility function expects a " + "Call node, but found 'NoneType'." in str(info.value)) # visitor error call = Call.create(RoutineSymbol("hello"), [ ("name", Literal("1.0", REAL_TYPE)), Literal("2.0", REAL_TYPE)]) with pytest.raises(VisitorError) as info: - _validate_named_args(call) + fortran_writer._gen_arguments(call) assert ("Fortran expects all named arguments to occur after all " "positional arguments but this is not the case for " "Call[name='hello']" in str(info.value)) # ok call = Call.create(RoutineSymbol("hello"), [ Literal("1.0", REAL_TYPE), ("name", Literal("2.0", REAL_TYPE))]) - _validate_named_args(call) + output = fortran_writer._gen_arguments(call) + assert isinstance(output, str) + assert output == "1.0, name=2.0" def test_fw_gen_use(fortran_writer): @@ -688,6 +677,14 @@ def test_fw_gen_vardecl(fortran_writer): result = fortran_writer.gen_vardecl(symbol) assert result == "integer, parameter :: dummy3 = 10\n" + # Constant with top level intrinsic + initval = IntrinsicCall.create(IntrinsicCall.Intrinsic.SIN, + [Literal("10", INTEGER_TYPE)]) + symbol = DataSymbol("dummy3i", INTEGER_TYPE, is_constant=True, + initial_value=initval) + result = fortran_writer.gen_vardecl(symbol) + assert result == "integer, parameter :: dummy3i = SIN(10)\n" + # Symbol has initial value but is not constant (static). This is a property # of the Fortran language and therefore is only checked for when we attempt # to generate Fortran. @@ -1031,117 +1028,6 @@ def test_fw_container_4(fortran_writer): " contains\n\n" "end module test\n" in fortran_writer(container)) -# assignment and binaryoperation (not intrinsics) are already checked -# within previous tests - - -@pytest.mark.parametrize("binary_intrinsic", ["mod", "max", "min", - "sign"]) -def test_fw_binaryoperator(fortran_writer, binary_intrinsic, tmpdir, - fortran_reader): - '''Check the FortranWriter class binary_operation method correctly - prints out the Fortran representation of an intrinsic. Tests all - of the binary operators, apart from sum (as it requires different - data types so is tested separately) and matmul ( as it requires - its arguments to be arrays). - - ''' - # Generate fparser2 parse tree from Fortran code. - code = ( - f"module test\n" - f"contains\n" - f"subroutine tmp(a, n)\n" - f" integer, intent(in) :: n\n" - f" real, intent(out) :: a(n)\n" - f" a = {binary_intrinsic}(1.0,1.0)\n" - f"end subroutine tmp\n" - f"end module test") - schedule = fortran_reader.psyir_from_source(code) - - # Generate Fortran from the PSyIR schedule - result = fortran_writer(schedule) - assert f"a = {binary_intrinsic.upper()}(1.0, 1.0)" in result - assert Compile(tmpdir).string_compiles(result) - - -def test_fw_binaryoperator_namedarg( - fortran_writer, tmpdir, fortran_reader, monkeypatch): - '''Check the FortranWriter class binary_operation method operator - correctly prints out the Fortran representation of an intrinsic - with a named argument. The dot_oroduct intrinsic is used - here. Also check that the expected exception is raised if all of - the named arguments are not after the positional arguments. - - ''' - # Generate fparser2 parse tree from Fortran code. - code = ( - "module test\n" - "contains\n" - "subroutine tmp(array, n)\n" - " integer, intent(in) :: n\n" - " real, intent(out) :: array(n)\n" - " integer :: a\n" - " a = dot_product(vector_a=array,vector_b=array)\n" - "end subroutine tmp\n" - "end module test") - schedule = fortran_reader.psyir_from_source(code) - - # Generate Fortran from the PSyIR schedule - result = fortran_writer(schedule) - assert "a = DOT_PRODUCT(vector_a=array, vector_b=array)" in result - assert Compile(tmpdir).string_compiles(result) - - # FileContainer->Container->Routine->Assignment->IntrinsicCall - intrinsic_call = schedule.children[0].children[0][0].children[1] - ref1, _ = intrinsic_call._argument_names[0] - ref2, _ = intrinsic_call._argument_names[1] - monkeypatch.setattr( - intrinsic_call, "_argument_names", [(ref1, "array"), (ref2, None)]) - with pytest.raises(VisitorError) as info: - _ = fortran_writer(schedule) - assert ("Fortran expects all named arguments to occur after all " - "positional arguments but this is not the case for " - "BinaryOperation[operator:'DOT_PRODUCT']" in str(info.value)) - - -def test_fw_binaryoperator_namedarg2(fortran_writer): - '''Check the FortranWriter class binary_operation method operator - correctly outputs the Fortran representation of an intrinsic with - its first argument being a named argument. - - ''' - operator = BinaryOperation.create( - BinaryOperation.Operator.MIN, - ("test1", Literal("1.0", REAL_TYPE)), - ("test2", Literal("2.0", REAL_TYPE))) - result = fortran_writer(operator) - assert result == "MIN(test1=1.0, test2=2.0)" - - -def test_fw_binaryoperator_matmul(fortran_writer, tmpdir, fortran_reader): - '''Check the FortranWriter class binary_operation method with the matmul - operator correctly prints out the Fortran representation of an - intrinsic. - - ''' - # Generate fparser2 parse tree from Fortran code. - code = ( - "module test\n" - "contains\n" - "subroutine tmp(a, b, c, n)\n" - " integer, intent(in) :: n\n" - " real, intent(in) :: a(n,n), b(n)\n" - " real, intent(out) :: c(n)\n" - " c = MATMUL(a,b)\n" - "end subroutine tmp\n" - "end module test") - schedule = fortran_reader.psyir_from_source(code) - - # Generate Fortran from the PSyIR schedule - result = fortran_writer(schedule) - assert "c = MATMUL(a, b)" in result - assert Compile(tmpdir).string_compiles(result) - def test_fw_binaryoperator_unknown(fortran_reader, fortran_writer, monkeypatch): @@ -1156,13 +1042,13 @@ def test_fw_binaryoperator_unknown(fortran_reader, fortran_writer, "subroutine tmp(a, n)\n" " integer, intent(in) :: n\n" " real, intent(out) :: a(n)\n" - " a = sign(1.0,1.0)\n" + " a = 1.0 * 1.0\n" "end subroutine tmp\n" "end module test") schedule = fortran_reader.psyir_from_source(code) - # Remove sign() from the list of supported binary operators + # Remove MUL from the list of supported binary operators monkeypatch.delitem(fortran_writer._operator_2_str, - BinaryOperation.Operator.SIGN) + BinaryOperation.Operator.MUL) # Generate Fortran from the PSyIR schedule with pytest.raises(VisitorError) as excinfo: _ = fortran_writer(schedule) @@ -1284,73 +1170,6 @@ def test_fw_naryoperator(fortran_reader, fortran_writer, tmpdir): assert Compile(tmpdir).string_compiles(result) -def test_fw_naryoperator_namedarg( - fortran_writer, tmpdir, fortran_reader, monkeypatch): - '''Check the FortranWriter class nary_operation method operator - correctly prints out the Fortran representation of an intrinsic - with a named argument. The MIN operator is used here. Also check - that the expected exception is raised if all of the named - arguments are not after the positional arguments (which requires - monkeypatching). - - ''' - # Generate fparser2 parse tree from Fortran code. - code = ( - "module test\n" - "contains\n" - "subroutine tmp(a)\n" - " integer, intent(out) :: a\n" - " a = min(a1=1.0, a2=2.0, a3=3.0)\n" - "end subroutine tmp\n" - "end module test") - schedule = fortran_reader.psyir_from_source(code) - - # Generate Fortran from the PSyIR schedule - result = fortran_writer(schedule) - assert "a = MIN(a1=1.0, a2=2.0, a3=3.0)" in result - assert Compile(tmpdir).string_compiles(result) - - # monkeypatch the min operator argument names - # FileContainer->Container->Routine->Assignment->NaryOperation - min_operator = schedule.children[0].children[0][0].children[1] - ref_name1 = min_operator._argument_names[0] - ref_name2 = min_operator._argument_names[1] - ref3, _ = min_operator._argument_names[2] - monkeypatch.setattr(min_operator, "_argument_names", - [ref_name1, ref_name2, (ref3, None)]) - with pytest.raises(VisitorError) as info: - _ = fortran_writer(schedule) - print(str(info.value)) - assert ("Fortran expects all named arguments to occur after all " - "positional arguments but this is not the case for " - "NaryOperation[operator:'MIN']" in str(info.value)) - - -def test_fw_naryoperator_unknown(fortran_reader, fortran_writer, monkeypatch): - ''' Check that the FortranWriter class nary_operation method raises - the expected error if it encounters an unknown operator. - - ''' - # Generate fparser2 parse tree from Fortran code. - code = ( - "module test\n" - "contains\n" - "subroutine tmp(a, n)\n" - " integer, intent(in) :: n\n" - " real, intent(out) :: a\n" - " a = max(1.0,1.0,2.0)\n" - "end subroutine tmp\n" - "end module test") - schedule = fortran_reader.psyir_from_source(code) - # Remove max() from the list of supported nary operators - monkeypatch.delitem(fortran_writer._operator_2_str, - NaryOperation.Operator.MAX) - # Generate Fortran from the PSyIR schedule - with pytest.raises(VisitorError) as err: - _ = fortran_writer(schedule) - assert "Unexpected N-ary op" in str(err.value) - - def test_fw_reference(fortran_reader, fortran_writer, tmpdir): '''Check the FortranWriter class reference method prints the appropriate information (the name of the reference it points to). @@ -1396,26 +1215,21 @@ def test_fw_range(fortran_writer): one = Literal("1", INTEGER_TYPE) two = Literal("2", INTEGER_TYPE) three = Literal("3", INTEGER_TYPE) - dim1_bound_start = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol), - one.copy()) - dim1_bound_stop = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(symbol), - one.copy()) - dim2_bound_start = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol), - Literal("2", INTEGER_TYPE)) - dim3_bound_start = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol), - Literal("3", INTEGER_TYPE)) - dim3_bound_stop = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(symbol), - Literal("3", INTEGER_TYPE)) + dim1_bound_start = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), ("dim", one.copy())]) + dim1_bound_stop = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(symbol), ("dim", one.copy())]) + dim2_bound_start = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), ("dim", two.copy())]) + dim3_bound_start = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), ("dim", three.copy())]) + dim3_bound_stop = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(symbol), ("dim", three.copy())]) plus = BinaryOperation.create( BinaryOperation.Operator.ADD, Reference(DataSymbol("b", REAL_TYPE)), @@ -1424,9 +1238,9 @@ def test_fw_range(fortran_writer): range2 = Range.create(dim2_bound_start, plus, step=three) # Check the ranges in isolation result = fortran_writer(range1) - assert result == "1:UBOUND(a, 1)" + assert result == "1:UBOUND(a, dim=1)" result = fortran_writer(range2) - assert result == "LBOUND(a, 2):b + c:3" + assert result == "LBOUND(a, dim=2):b + c:3" # Check the ranges in context array = ArrayReference.create( symbol, [range1, range2]) @@ -1448,14 +1262,12 @@ def test_fw_range(fortran_writer): # output. array_type = ArrayType(REAL_TYPE, [10]) symbol_b = DataSymbol("b", array_type) - b_dim1_bound_start = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol_b), - one.copy()) - b_dim1_bound_stop = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(symbol_b), - one.copy()) + b_dim1_bound_start = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol_b), ("dim", one.copy())]) + b_dim1_bound_stop = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(symbol_b), ("dim", one.copy())]) array = ArrayReference.create( symbol, [Range.create(b_dim1_bound_start, b_dim1_bound_stop), @@ -1463,8 +1275,8 @@ def test_fw_range(fortran_writer): Range.create(dim3_bound_stop.copy(), dim3_bound_start.copy(), step=three.copy())]) result = fortran_writer.arrayreference_node(array) - assert result == ("a(LBOUND(b, 1):UBOUND(b, 1),:2:3," - "UBOUND(a, 3):LBOUND(a, 3):3)") + assert result == ("a(LBOUND(b, dim=1):UBOUND(b, dim=1),:2:3," + "UBOUND(a, dim=3):LBOUND(a, dim=3):3)") def test_fw_range_structureref(fortran_writer): @@ -1478,49 +1290,58 @@ def test_fw_range_structureref(fortran_writer): one = Literal("1", INTEGER_TYPE) two = Literal("2", INTEGER_TYPE) data_ref = StructureReference.create(symbol, ["data"]) - start = BinaryOperation.create(BinaryOperation.Operator.LBOUND, - data_ref.copy(), one.copy()) - stop = BinaryOperation.create(BinaryOperation.Operator.UBOUND, - data_ref.copy(), one.copy()) + start = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [data_ref.copy(), ("dim", one.copy())]) + stop = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [data_ref.copy(), ("dim", one.copy())]) ref = StructureReference.create(symbol, [("data", [Range.create(start, stop)])]) result = fortran_writer(ref) assert result == "my_grid%data(:)" - start = BinaryOperation.create(BinaryOperation.Operator.LBOUND, - Reference(array_symbol), one.copy()) - stop = BinaryOperation.create(BinaryOperation.Operator.UBOUND, - Reference(array_symbol), one.copy()) + start = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(array_symbol), ("dim", one.copy())]) + stop = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(array_symbol), ("dim", one.copy())]) range2 = Range.create(start, stop) result = fortran_writer( ArrayOfStructuresReference.create(array_symbol, [range2], [("data", [range2.copy()])])) assert (result == - "my_grids(:)%data(LBOUND(my_grids, 1):UBOUND(my_grids, 1))") + "my_grids(:)%data(LBOUND(my_grids, dim=1):" + "UBOUND(my_grids, dim=1))") symbol = DataSymbol("field", DeferredType()) int_one = Literal("1", INTEGER_TYPE) - lbound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - StructureReference.create(symbol, ["first"]), int_one.copy()) - ubound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - StructureReference.create(symbol, ["first"]), int_one.copy()) + lbound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [StructureReference.create(symbol, ["first"]), + ("dim", int_one.copy())]) + ubound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [StructureReference.create(symbol, ["first"]), + ("dim", int_one.copy())]) my_range = Range.create(lbound, ubound) ref = ArrayOfStructuresReference.create(symbol, [my_range.copy()], ["first", ("second", [my_range.copy()])]) result = fortran_writer(ref) assert (result == - "field(LBOUND(field%first, 1):" - "UBOUND(field%first, 1))%first%second(LBOUND(field%first, 1):" - "UBOUND(field%first, 1))") + "field(LBOUND(field%first, dim=1):" + "UBOUND(field%first, dim=1))%first%second(" + "LBOUND(field%first, dim=1):UBOUND(field%first, dim=1))") data_ref = Reference(array_symbol) - start = BinaryOperation.create(BinaryOperation.Operator.LBOUND, - data_ref.copy(), two.copy()) - stop = BinaryOperation.create(BinaryOperation.Operator.UBOUND, - data_ref.copy(), two.copy()) + start = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [data_ref.copy(), ("dim", two.copy())]) + stop = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [data_ref.copy(), ("dim", two.copy())]) aref = ArrayOfStructuresReference.create( array_symbol, [one.copy(), Range.create(start, stop)], ["flag"]) result = fortran_writer(aref) @@ -1647,18 +1468,6 @@ def test_fw_unaryoperator2(fortran_reader, fortran_writer, tmpdir): assert Compile(tmpdir).string_compiles(result) -def test_fw_unaryoperator_namedarg(fortran_writer): - '''Check the FortranWriter class unary_operation method operator - correctly outputs the Fortran representation of an intrinsic with - a named argument. - - ''' - intrinsic = UnaryOperation.create( - UnaryOperation.Operator.COS, ("value", Literal("1.0", REAL_TYPE))) - result = fortran_writer(intrinsic) - assert result == "COS(value=1.0)" - - def test_fw_unaryoperator_unknown(fortran_reader, fortran_writer, monkeypatch): '''Check the FortranWriter class unary_operation method raises an exception if an unknown unary operator is found. @@ -1671,13 +1480,13 @@ def test_fw_unaryoperator_unknown(fortran_reader, fortran_writer, monkeypatch): "subroutine tmp(a, n)\n" " integer, intent(in) :: n\n" " real, intent(out) :: a(n)\n" - " a = sin(1.0)\n" + " a = -1.0\n" "end subroutine tmp\n" "end module test") schedule = fortran_reader.psyir_from_source(code) - # Remove sin() from the dict of unary operators + # Remove MINUS from the dict of unary operators monkeypatch.delitem(fortran_writer._operator_2_str, - UnaryOperation.Operator.SIN) + UnaryOperation.Operator.MINUS) # Generate Fortran from the PSyIR schedule with pytest.raises(VisitorError) as excinfo: _ = fortran_writer(schedule) @@ -1987,7 +1796,7 @@ def test_fw_call_node(fortran_writer): symbol_table.add(symbol_call) mult_ab = BinaryOperation.create( BinaryOperation.Operator.MUL, ref_a.copy(), ref_b.copy()) - max_ab = NaryOperation.create(NaryOperation.Operator.MAX, [ref_a, ref_b]) + max_ab = IntrinsicCall.create(IntrinsicCall.Intrinsic.MAX, [ref_a, ref_b]) call = Call.create(symbol_call, [mult_ab, max_ab]) schedule = KernelSchedule.create("work", symbol_table, [call]) # Generate Fortran from the PSyIR schedule diff --git a/src/psyclone/tests/psyir/backend/sir_test.py b/src/psyclone/tests/psyir/backend/sir_test.py index 3a8b5961f6..efb4f7404c 100644 --- a/src/psyclone/tests/psyir/backend/sir_test.py +++ b/src/psyclone/tests/psyir/backend/sir_test.py @@ -45,7 +45,9 @@ from psyclone.psyGen import PSyFactory from psyclone.psyir.backend.sir import gen_stencil, SIRWriter from psyclone.psyir.backend.visitor import VisitorError -from psyclone.psyir.nodes import Schedule, Assignment, Node +from psyclone.psyir.nodes import ( + Schedule, Assignment, Node, BinaryOperation, UnaryOperation, Literal) +from psyclone.psyir.symbols import INTEGER_TYPE # pylint: disable=redefined-outer-name @@ -586,10 +588,21 @@ def test_sirwriter_binaryoperation_node_3(parser, sir_writer): " )" in result) -# (4/4) Method binaryoperation_node -def test_sirwriter_binaryoperation_node_4(parser, sir_writer): - '''Check the binaryoperation_node method of the SIRWriter class raises - the expected exception if an unsupported binary operator is found. +def test_sirwriter_binaryoperator_not_supported(sir_writer): + ''' Check that unsupported BinaryOperators produce a relevant error. ''' + operation = BinaryOperation.create( + BinaryOperation.Operator.REM, + Literal("1", INTEGER_TYPE), + Literal("2", INTEGER_TYPE)) + with pytest.raises(VisitorError) as excinfo: + sir_writer.binaryoperation_node(operation) + assert ("Method binaryoperation_node in class SIRWriter, unsupported " + "operator 'Operator.REM' found." in str(excinfo.value)) + + +def test_sirwriter_intrinsiccall_node(parser, sir_writer): + '''Check the intrinsiccall_node method of the SIRWriter class raises + the expected exception if an unsupported intrinsic is found. ''' code = CODE.replace("\n integer ::", @@ -599,8 +612,8 @@ def test_sirwriter_binaryoperation_node_4(parser, sir_writer): code = code.replace("a(i,j,k) = 1.0", "a(i,j,k) = matmul(b, c)") rhs = get_rhs(parser, code) with pytest.raises(VisitorError) as excinfo: - _ = sir_writer.binaryoperation_node(rhs) - assert "unsupported operator 'Operator.MATMUL' found" in str(excinfo.value) + _ = sir_writer.intrinsiccall_node(rhs) + assert "unsupported intrinsic 'MATMUL' found" in str(excinfo.value) # (1/2) Method reference_node @@ -700,21 +713,6 @@ def test_sirwriter_unaryoperation_node_1(parser, sir_writer): in result) -# (2/5) Method unaryoperation_node -def test_sirwriter_unary_node_2(parser, sir_writer): - '''Check the unaryoperation_node method of the SIRWriter class raises - the expected exception if an unsupported unary operator is found. - - ''' - # Choose the sin function as there are no examples of its - # use in the SIR so no mapping is currently provided. - code = CODE.replace("1.0", "sin(1.0)") - rhs = get_rhs(parser, code) - with pytest.raises(VisitorError) as excinfo: - _ = sir_writer.unaryoperation_node(rhs) - assert "unsupported operator 'Operator.SIN' found" in str(excinfo.value) - - # (3/5) Method unaryoperation_node @pytest.mark.parametrize( "value, datatype", [("-1", "Integer"), ("-1.0", "Float")]) @@ -772,6 +770,17 @@ def test_sirwriter_unary_node_5(parser, sir_writer): ")\n") +def test_sirwriter_unaryoperator_not_supported(sir_writer): + ''' Check that unsupported UnaryOperators produce a relevant error. ''' + operation = UnaryOperation.create( + UnaryOperation.Operator.NOT, + Literal("1", INTEGER_TYPE)) + with pytest.raises(VisitorError) as excinfo: + sir_writer.unaryoperation_node(operation) + assert ("Method unaryoperation_node in class SIRWriter, unsupported " + "operator 'Operator.NOT' found." in str(excinfo.value)) + + # (1/4) Method ifblock_node def test_sirwriter_ifblock_node_1(parser, sir_writer): '''Check the ifblock_node method of the SIRWriter class @@ -942,47 +951,46 @@ def test_sirwriter_schedule_node_1(parser, sir_writer): " \"=\")," in schedule_result) -def test_sirwriter_unaryoperation_intrinsic_node(parser, sir_writer): - '''Check the unaryoperation_node method of the SIRWriter class - outputs the expected SIR code when the unaryoperation is an - intrinsic. +def test_sirwriter_intrinsiccall_node_2(parser, sir_writer): + '''Check the intrinsiccall_node method of the SIRWriter class + outputs the expected SIR code for a supported intrinsic with + 1 argument. ''' code = CODE.replace("1.0", "abs(1.0)") rhs = get_rhs(parser, code) - result = sir_writer.unaryoperation_node(rhs) + result = sir_writer.intrinsiccall_node(rhs) assert ("make_fun_call_expr(\"math::fabs\", [make_literal_access_expr(" "\"1.0\", BuiltinType.Float)])" in result) -@pytest.mark.parametrize("operation", ["min", "max"]) -def test_sirwriter_binaryoperation_intrinsic_node(parser, sir_writer, - operation): - '''Check the binaryoperation_node method of the SIRWriter class - outputs the expected SIR code when the binaryoperation is an - intrinsic. +@pytest.mark.parametrize("intrinsic", ["min", "max"]) +def test_sirwriter_intrinsiccall_node_3(parser, sir_writer, intrinsic): + '''Check the intrinsiccall_node method of the SIRWriter class + outputs the expected SIR code for a supported intrinsic with 2 + arguments. ''' - code = CODE.replace("1.0", f"{operation}(1.0, 2.0)") + code = CODE.replace("1.0", f"{intrinsic}(1.0, 2.0)") rhs = get_rhs(parser, code) - result = sir_writer.binaryoperation_node(rhs) - assert (f"make_fun_call_expr(\"math::{operation}\", [" + result = sir_writer.intrinsiccall_node(rhs) + assert (f"make_fun_call_expr(\"math::{intrinsic}\", [" f"make_literal_access_expr(\"1.0\", BuiltinType.Float)], " f"[make_literal_access_expr(\"2.0\", BuiltinType.Float)])" in result) -def test_sirwriter_binaryoperation_sign_node(parser, sir_writer): - '''Check the binaryoperation_node method of the SIRWriter class - outputs the expected SIR code when the binaryoperation is sign - intrinsic. This is a special case as the sign intrinsic is +def test_sirwriter_intrinsiccall_sign_node(parser, sir_writer): + '''Check the intrinsiccall_node method of the SIRWriter class + outputs the expected SIR code for the sign intrinsic. + This is a special case as the sign intrinsic is implemented differently in the PSyIR (Fortran implementation) and SIR (C implementation). ''' code = CODE.replace("1.0", "sign(1.0, 2.0)") rhs = get_rhs(parser, code) - result = sir_writer.binaryoperation_node(rhs) + result = sir_writer.intrinsiccall_node(rhs) assert ("make_binary_operator(make_fun_call_expr(\"math::fabs\", " "[make_literal_access_expr(\"1.0\", BuiltinType.Float)]), " "\"*\", make_fun_call_expr(\"math::sign\", " @@ -990,37 +998,4 @@ def test_sirwriter_binaryoperation_sign_node(parser, sir_writer): in result) -def test_sirwriter_naryoperation_error(parser, sir_writer, monkeypatch): - '''Check the naryoperation_node method of the SIRWriter class raises - the expected exception when the naryoperation is an unsupported - operator. Both min and max are supported so we need to - monkeypatch. - - ''' - code = CODE.replace("1.0", "MIN(1.0, 2.0, 3.0)") - rhs = get_rhs(parser, code) - monkeypatch.setattr(rhs, "_operator", None) - with pytest.raises(VisitorError) as info: - _ = sir_writer.naryoperation_node(rhs) - assert ("Method naryoperation_node in class SIRWriter, unsupported " - "operator 'None' found. Expected one of '['MIN', 'MAX']'." - in str(info.value)) - - -@pytest.mark.parametrize("operation", ["min", "max"]) -def test_sirwriter_naryoperation_intrinsic_node(parser, sir_writer, operation): - '''Check the naryoperation_node method of the SIRWriter class - outputs the expected SIR code when the naryoperation is an - intrinsic. - - ''' - code = CODE.replace("1.0", f"{operation}(1.0, 2.0, 3.0)") - rhs = get_rhs(parser, code) - result = sir_writer.naryoperation_node(rhs) - assert (f"make_fun_call_expr(\"math::{operation}\", " - f"[make_literal_access_expr(\"1.0\", BuiltinType.Float)], " - f"[make_literal_access_expr(\"2.0\", BuiltinType.Float)], " - f"[make_literal_access_expr(\"3.0\", BuiltinType.Float)])" - in result) - # Class SIRWriter end diff --git a/src/psyclone/tests/psyir/frontend/fortran_test.py b/src/psyclone/tests/psyir/frontend/fortran_test.py index 7e364cb558..fe02010a7a 100644 --- a/src/psyclone/tests/psyir/frontend/fortran_test.py +++ b/src/psyclone/tests/psyir/frontend/fortran_test.py @@ -42,7 +42,7 @@ from psyclone.psyir.frontend.fortran import FortranReader from psyclone.psyir.frontend.fparser2 import Fparser2Reader from psyclone.psyir.nodes import Routine, FileContainer, UnaryOperation, \ - BinaryOperation, Literal, Assignment, CodeBlock + BinaryOperation, Literal, Assignment, CodeBlock, IntrinsicCall from psyclone.psyir.symbols import SymbolTable, DataSymbol, \ ScalarType, SymbolError, ContainerSymbol, DeferredType @@ -120,8 +120,8 @@ def test_fortran_psyir_from_expression(fortran_reader): assert isinstance(psyir.children[0].children[0], Literal) assert psyir.children[0].children[0].value == "3.0" psyir = fortran_reader.psyir_from_expression("ABS(-3.0)", table) - assert isinstance(psyir, UnaryOperation) - assert psyir.operator == UnaryOperation.Operator.ABS + assert isinstance(psyir, IntrinsicCall) + assert psyir.intrinsic == IntrinsicCall.Intrinsic.ABS assert isinstance(psyir.children[0], UnaryOperation) assert psyir.children[0].operator == UnaryOperation.Operator.MINUS assert isinstance(psyir.children[0].children[0], Literal) @@ -216,7 +216,7 @@ def test_fortran_psyir_from_file(fortran_reader, tmpdir_factory): ''' Test that the psyir_from_file method reads and parses to PSyIR the specified file. ''' filename = str(tmpdir_factory.mktemp('frontend_test').join("testfile.f90")) - with open(filename, "w") as wfile: + with open(filename, "w", encoding='utf-8') as wfile: wfile.write(CODE) # Check with a proper file @@ -227,7 +227,7 @@ def test_fortran_psyir_from_file(fortran_reader, tmpdir_factory): # Check with an empty file filename = str(tmpdir_factory.mktemp('frontend_test').join("empty.f90")) - with open(filename, "w") as wfile: + with open(filename, "w", encoding='utf-8') as wfile: wfile.write("") file_container = fortran_reader.psyir_from_file(filename) assert isinstance(file_container, FileContainer) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_alloc_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_alloc_handler_test.py index b29658f14c..9d37e494d6 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_alloc_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_alloc_handler_test.py @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: A. R. Porter, STFC Daresbury Lab - +# Modified: S. Siso, STFC Daresbury Lab ''' Performs pytest tests on the support for allocate statements in the fparser2 PSyIR front-end. ''' @@ -117,7 +117,6 @@ def test_alloc_with_mold_or_source(fortran_reader): integer, parameter :: mask(5,8) real, allocatable, dimension(:, :) :: var1, var2 allocate(var1, mold=mask, stat=ierr) - var1(:,:) = 3.1459 allocate(var2, source=var1) end program test_alloc ''' diff --git a/src/psyclone/tests/psyir/frontend/fparser2_bound_intrinsic_test.py b/src/psyclone/tests/psyir/frontend/fparser2_bound_intrinsic_test.py index 666852e82a..5ba826daaf 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_bound_intrinsic_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_bound_intrinsic_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020, Science and Technology Facilities Council. +# Copyright (c) 2020-23, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -31,15 +31,17 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -# Author A. R. Porter, STFC Daresbury Laboratory +# Author: A. R. Porter, STFC Daresbury Laboratory +# Modified: S. Siso, STFC Daresbury Laboratory ''' Module containing pytest tests for the handling of the U/LBOUND intrinsics in the PSyIR. ''' -from __future__ import absolute_import - import pytest from fparser.common.readfortran import FortranStringReader +from fparser.two.Fortran2003 import Execution_Part +from psyclone.psyir.nodes import ( + Schedule, Assignment, BinaryOperation, Reference, Literal, IntrinsicCall) from psyclone.psyir.frontend.fparser2 import Fparser2Reader @@ -55,20 +57,17 @@ def test_bound_intrinsics(bound, expression): TODO #754 fix test so that 'disable_declaration_check' fixture is not required. ''' - from fparser.two.Fortran2003 import Execution_Part - from psyclone.psyir.nodes import Schedule, Assignment, BinaryOperation, \ - Reference, Literal fake_parent = Schedule() processor = Fparser2Reader() reader = FortranStringReader(expression.format(bound)) fp2intrinsic = Execution_Part(reader).content[0] processor.process_nodes(fake_parent, [fp2intrinsic]) assert isinstance(fake_parent[0], Assignment) - assert isinstance(fake_parent[0].rhs, BinaryOperation) + assert isinstance(fake_parent[0].rhs, IntrinsicCall) if bound == "ubound": - assert fake_parent[0].rhs.operator == BinaryOperation.Operator.UBOUND + assert fake_parent[0].rhs.intrinsic == IntrinsicCall.Intrinsic.UBOUND else: - assert fake_parent[0].rhs.operator == BinaryOperation.Operator.LBOUND + assert fake_parent[0].rhs.intrinsic == IntrinsicCall.Intrinsic.LBOUND assert isinstance(fake_parent[0].rhs.children[0], Reference) assert isinstance(fake_parent[0].rhs.children[1], (Literal, BinaryOperation)) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py b/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py index ef881c7bdb..4dccea0b28 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -31,7 +31,7 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -# Authors: A. R. Porter and N. Nobre, STFC Daresbury Lab +# Authors: A. R. Porter, N. Nobre and S. Siso, STFC Daresbury Lab # ----------------------------------------------------------------------------- ''' Performs py.test tests on the fparser2 PSyIR front-end support for @@ -47,7 +47,7 @@ from psyclone.psyir.nodes import KernelSchedule, CodeBlock, Assignment, \ ArrayOfStructuresReference, StructureReference, Member, StructureMember, \ ArrayOfStructuresMember, ArrayMember, Literal, Reference, Range, \ - BinaryOperation + IntrinsicCall from psyclone.psyir.symbols import SymbolError, DeferredType, StructureType, \ DataTypeSymbol, ScalarType, RoutineSymbol, Symbol, ArrayType, \ UnknownFortranType, DataSymbol, INTEGER_TYPE, ContainerSymbol, \ @@ -391,21 +391,22 @@ def test_derived_type_ref(f2008_parser, fortran_writer): assert isinstance(amem, ArrayOfStructuresMember) assert isinstance(amem.member, ArrayMember) assert isinstance(amem.member.indices[0], Range) - bop = amem.member.indices[0].children[0] - assert isinstance(bop, BinaryOperation) - assert bop.children[0].symbol.name == "var" - assert isinstance(bop.children[0], StructureReference) - # The argument to the LBOUND binary operator must ultimately resolve down + lbound = amem.member.indices[0].children[0] + assert isinstance(lbound, IntrinsicCall) + assert lbound.intrinsic == IntrinsicCall.Intrinsic.LBOUND + assert lbound.children[0].symbol.name == "var" + assert isinstance(lbound.children[0], StructureReference) + # The argument to the LBOUND intrinsic must ultimately resolve down # to a Member access, not an ArrayMember access. - assert isinstance(bop.children[0].member.member.member, Member) - assert not isinstance(bop.children[0].member.member.member, ArrayMember) - bop = amem.member.indices[0].children[1] - assert isinstance(bop, BinaryOperation) - assert bop.operator == BinaryOperation.Operator.UBOUND - # The argument to the UBOUND binary operator must ultimately resolve down + assert isinstance(lbound.children[0].member.member.member, Member) + assert not isinstance(lbound.children[0].member.member.member, ArrayMember) + ubound = amem.member.indices[0].children[1] + assert isinstance(ubound, IntrinsicCall) + assert ubound.intrinsic == IntrinsicCall.Intrinsic.UBOUND + # The argument to the UBOUND intrinsic must ultimately resolve down # to a Member access, not an ArrayMember access. - assert isinstance(bop.children[0].member.member.member, Member) - assert not isinstance(bop.children[0].member.member.member, ArrayMember) + assert isinstance(ubound.children[0].member.member.member, Member) + assert not isinstance(ubound.children[0].member.member.member, ArrayMember) gen = fortran_writer(amem) assert gen == "subgrid(3)%data(:)" # var%region%subgrid(3)%data(var%start:var%stop) @@ -420,38 +421,38 @@ def test_derived_type_ref(f2008_parser, fortran_writer): assign = assignments[5] amem = assign.lhs data_node = amem.member.member.member - bop = data_node.children[0].children[0] - assert isinstance(bop, BinaryOperation) - assert bop.operator == BinaryOperation.Operator.LBOUND + lbound = data_node.children[0].children[0] + assert isinstance(lbound, IntrinsicCall) + assert lbound.intrinsic == IntrinsicCall.Intrinsic.LBOUND # Argument to LBOUND must be a Member, not an ArrayMember - assert isinstance(bop.children[0].member.member.member, Member) - assert not isinstance(bop.children[0].member.member.member, ArrayMember) - bop = data_node.children[0].children[1] - assert isinstance(bop, BinaryOperation) - assert bop.operator == BinaryOperation.Operator.UBOUND + assert isinstance(lbound.children[0].member.member.member, Member) + assert not isinstance(lbound.children[0].member.member.member, ArrayMember) + ubound = data_node.children[0].children[1] + assert isinstance(ubound, IntrinsicCall) + assert ubound.intrinsic == IntrinsicCall.Intrinsic.UBOUND # Argument to UBOUND must be a Member, not an ArrayMember - assert isinstance(bop.children[0].member.member.member, Member) - assert not isinstance(bop.children[0].member.member.member, ArrayMember) + assert isinstance(ubound.children[0].member.member.member, Member) + assert not isinstance(ubound.children[0].member.member.member, ArrayMember) # vars(1)%region%subgrid(:)%data(1) = 1.0 assign = assignments[6] amem = assign.lhs assert isinstance(amem.member.member.children[1], Range) - bop = amem.member.member.children[1].children[0] - assert isinstance(bop, BinaryOperation) - assert bop.operator == BinaryOperation.Operator.LBOUND - assert bop.children[0].member.member.name == "subgrid" - assert isinstance(bop.children[0].member.member, Member) - assert not isinstance(bop.children[0].member.member, ArrayMember) + lbound = amem.member.member.children[1].children[0] + assert isinstance(lbound, IntrinsicCall) + assert lbound.intrinsic == IntrinsicCall.Intrinsic.LBOUND + assert lbound.children[0].member.member.name == "subgrid" + assert isinstance(lbound.children[0].member.member, Member) + assert not isinstance(lbound.children[0].member.member, ArrayMember) assert amem.member.member.member.name == "data" assert isinstance(amem.member.member.member, ArrayMember) # vars(:)%region%subgrid(3)%xstop assign = assignments[7] amem = assign.lhs - bop = amem.children[1].children[0] - assert isinstance(bop, BinaryOperation) - assert bop.operator == BinaryOperation.Operator.LBOUND - assert isinstance(bop.children[0], Reference) - assert bop.children[0].symbol.name == "vars" + lbound = amem.children[1].children[0] + assert isinstance(lbound, IntrinsicCall) + assert lbound.intrinsic == IntrinsicCall.Intrinsic.LBOUND + assert isinstance(lbound.children[0], Reference) + assert lbound.children[0].symbol.name == "vars" def test_array_of_derived_type_ref(f2008_parser): diff --git a/src/psyclone/tests/psyir/frontend/fparser2_intrinsic_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_intrinsic_handler_test.py index d4b3653942..afaefe3f8c 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_intrinsic_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_intrinsic_handler_test.py @@ -44,14 +44,13 @@ from fparser.common.readfortran import FortranStringReader from fparser.two import Fortran2003 -from fparser.two.Fortran2003 import Execution_Part, Name +from fparser.two.Fortran2003 import Execution_Part from psyclone.errors import InternalError from psyclone.psyir.frontend.fparser2 import ( Fparser2Reader, _get_arg_names, _canonicalise_minmaxsum) from psyclone.psyir.nodes import ( - UnaryOperation, BinaryOperation, NaryOperation, Schedule, Assignment, - Reference, IntrinsicCall, CodeBlock) + Schedule, Assignment, Reference, IntrinsicCall, CodeBlock) from psyclone.psyir.symbols import ( REAL_TYPE, DataSymbol, UnknownFortranType, INTEGER_TYPE, SymbolTable, ArrayType, RoutineSymbol, AutomaticInterface) @@ -243,87 +242,97 @@ def test_intrinsic_handler_intrinsiccall_onearg( @pytest.mark.parametrize( - "code, expected_type, expected_op", - [('x = exp(a)', UnaryOperation, UnaryOperation.Operator.EXP), - ('x = sin(a)', UnaryOperation, UnaryOperation.Operator.SIN), - ('x = asin(a)', UnaryOperation, UnaryOperation.Operator.ASIN), - ('idx = ceiling(a)', UnaryOperation, UnaryOperation.Operator.CEIL), - ('x = abs(a)', UnaryOperation, UnaryOperation.Operator.ABS), - ('x = cos(a)', UnaryOperation, UnaryOperation.Operator.COS), - ('x = acos(a)', UnaryOperation, UnaryOperation.Operator.ACOS), - ('x = tan(a)', UnaryOperation, UnaryOperation.Operator.TAN), - ('x = atan(a)', UnaryOperation, UnaryOperation.Operator.ATAN), - ('x = real(a)', UnaryOperation, UnaryOperation.Operator.REAL), - ('x = real(a, 8)', BinaryOperation, BinaryOperation.Operator.REAL), - ('x = int(a)', UnaryOperation, UnaryOperation.Operator.INT), - ('x = int(a, 8)', BinaryOperation, BinaryOperation.Operator.INT), - ('x = log(a)', UnaryOperation, UnaryOperation.Operator.LOG), - ('x = log10(a)', UnaryOperation, UnaryOperation.Operator.LOG10), - ('x = mod(a, b)', BinaryOperation, BinaryOperation.Operator.REM), - ('x = matmul(a, b)', BinaryOperation, - BinaryOperation.Operator.MATMUL), - ('x = max(a, b)', BinaryOperation, BinaryOperation.Operator.MAX), - ('x = mAx(a, b, c)', NaryOperation, NaryOperation.Operator.MAX), - ('x = min(a, b)', BinaryOperation, BinaryOperation.Operator.MIN), - ('x = min(a, b, c)', NaryOperation, NaryOperation.Operator.MIN), - ('x = sign(a, b)', BinaryOperation, BinaryOperation.Operator.SIGN), - ('x = sqrt(a)', UnaryOperation, UnaryOperation.Operator.SQRT), - # Check that we get a CodeBlock for an unsupported unary operation - ('x = aimag(a)', CodeBlock, None), - # Check that we get a CodeBlock for an unsupported binary operation - ('x = dprod(a, b)', CodeBlock, None), - # Check that we get a CodeBlock for an unsupported N-ary operation - ('x = reshape(a, b, c)', CodeBlock, None), - # Check when the argument list is not an Actual_Arg_Spec_List for - # a unary operator - ('x = sin(-3.0)', UnaryOperation, UnaryOperation.Operator.SIN)]) + "code, expected_intrinsic", + [('x = exp(a)', IntrinsicCall.Intrinsic.EXP), + ('x = sin(a)', IntrinsicCall.Intrinsic.SIN), + ('x = asin(a)', IntrinsicCall.Intrinsic.ASIN), + ('idx = ceiling(a)', IntrinsicCall.Intrinsic.CEILING), + ('x = abs(a)', IntrinsicCall.Intrinsic.ABS), + ('x = cos(a)', IntrinsicCall.Intrinsic.COS), + ('x = acos(a)', IntrinsicCall.Intrinsic.ACOS), + ('x = tan(a)', IntrinsicCall.Intrinsic.TAN), + ('x = atan(a)', IntrinsicCall.Intrinsic.ATAN), + ('x = real(a)', IntrinsicCall.Intrinsic.REAL), + ('x = real(a, 8)', IntrinsicCall.Intrinsic.REAL), + ('x = int(a)', IntrinsicCall.Intrinsic.INT), + ('x = int(a, 8)', IntrinsicCall.Intrinsic.INT), + ('x = log(a)', IntrinsicCall.Intrinsic.LOG), + ('x = log10(a)', IntrinsicCall.Intrinsic.LOG10), + ('x = mod(a, b)', IntrinsicCall.Intrinsic.MOD), + ('x = matmul(a, b)', IntrinsicCall.Intrinsic.MATMUL), + ('x = max(a, b)', IntrinsicCall.Intrinsic.MAX), + ('x = mAx(a, b, c)', IntrinsicCall.Intrinsic.MAX), + ('x = min(a, b)', IntrinsicCall.Intrinsic.MIN), + ('x = min(a, b, c)', IntrinsicCall.Intrinsic.MIN), + ('x = sign(a, b)', IntrinsicCall.Intrinsic.SIGN), + ('x = sqrt(a)', IntrinsicCall.Intrinsic.SQRT), + ('x = aimag(a)', IntrinsicCall.Intrinsic.AIMAG), + ('x = dprod(a, b)', IntrinsicCall.Intrinsic.DPROD), + ('x = reshape(a, b, c)', IntrinsicCall.Intrinsic.RESHAPE), + ('x = sin(-3.0)', IntrinsicCall.Intrinsic.SIN)]) @pytest.mark.usefixtures("f2008_parser") -def test_handling_intrinsics(code, expected_type, expected_op, symbol_table): +def test_handling_intrinsics(code, expected_intrinsic, symbol_table): '''Test that the fparser2 _intrinsic_handler method deals with Intrinsic_Function_Reference nodes that are translated to PSyIR - Operation nodes. Includes tests for unsupported intrinsics that - are returned as codeblocks. + IntrinsicCall nodes. ''' processor = Fparser2Reader() fake_parent = Schedule(symbol_table=symbol_table) reader = FortranStringReader(code) fp2node = Execution_Part.match(reader)[0][0] - print(type(fp2node.children[2])) processor.process_nodes(fake_parent, [fp2node]) assign = fake_parent.children[0] assert isinstance(assign, Assignment) - assert isinstance(assign.rhs, expected_type), \ + assert isinstance(assign.rhs, IntrinsicCall), \ "Fails when parsing '" + code + "'" - if expected_type is not CodeBlock: - assert assign.rhs._operator == expected_op, \ - "Fails when parsing '" + code + "'" - assert len(assign.rhs.children) == len(assign.rhs.argument_names) - for named_arg in assign.rhs.argument_names: - assert named_arg is None + assert assign.rhs._intrinsic == expected_intrinsic, \ + "Fails when parsing '" + code + "'" + assert len(assign.rhs.children) == len(assign.rhs.argument_names) + for named_arg in assign.rhs.argument_names: + assert named_arg is None + + +def test_handling_unsupported_intrinsics(symbol_table): + '''Test that unsupported intrinsics are converted to codeblock. + (Note that all Fortran 2018 intrinsics are supported but there + are specific-type intrinsics and specific-compiler intrinsics + that may not be supported). This are returned as CodeBlocks. + ''' + processor = Fparser2Reader() + fake_parent = Schedule(symbol_table=symbol_table) + code = "x = sin(a)" + reader = FortranStringReader(code) + fp2node = Execution_Part.match(reader)[0][0] + # Change the instrinsic string name in order to create a new + # intrinsic which is not recognised by the PSyIR parser + fp2node.children[2].items[0].string = "Unsupported" + processor.process_nodes(fake_parent, [fp2node]) + assert not fake_parent.walk(IntrinsicCall) + assert isinstance(fake_parent.children[0].rhs, CodeBlock) @pytest.mark.parametrize( - "code, expected_type, expected_op, expected_names", - [('x = sin(a)', UnaryOperation, - UnaryOperation.Operator.SIN, [None]), - ('x = sin(array=a)', UnaryOperation, - UnaryOperation.Operator.SIN, ["array"]), - ('x = dot_product(a, b)', BinaryOperation, - BinaryOperation.Operator.DOT_PRODUCT, [None, None]), - ('x = dot_product(a, vector_b=b)', BinaryOperation, - BinaryOperation.Operator.DOT_PRODUCT, [None, "vector_b"]), - ('x = dot_product(vector_a=a, vector_b=b)', BinaryOperation, - BinaryOperation.Operator.DOT_PRODUCT, ["vector_a", "vector_b"]), - ('x = max(a, b, c)', NaryOperation, - NaryOperation.Operator.MAX, [None, None, None]), - ('x = max(a1=a, a2=b, a3=c)', NaryOperation, - NaryOperation.Operator.MAX, ["a1", "a2", "a3"]), - ('x = max(a, b, a3=c)', NaryOperation, - NaryOperation.Operator.MAX, [None, None, "a3"])]) + "code, expected_intrinsic, expected_names", + [('x = sin(a)', + IntrinsicCall.Intrinsic.SIN, [None]), + ('x = sin(array=a)', + IntrinsicCall.Intrinsic.SIN, ["array"]), + ('x = dot_product(a, b)', + IntrinsicCall.Intrinsic.DOT_PRODUCT, [None, None]), + ('x = dot_product(a, vector_b=b)', + IntrinsicCall.Intrinsic.DOT_PRODUCT, [None, "vector_b"]), + ('x = dot_product(vector_a=a, vector_b=b)', + IntrinsicCall.Intrinsic.DOT_PRODUCT, ["vector_a", "vector_b"]), + ('x = max(a, b, c)', + IntrinsicCall.Intrinsic.MAX, [None, None, None]), + ('x = max(a1=a, a2=b, a3=c)', + IntrinsicCall.Intrinsic.MAX, ["a1", "a2", "a3"]), + ('x = max(a, b, a3=c)', + IntrinsicCall.Intrinsic.MAX, [None, None, "a3"])]) @pytest.mark.usefixtures("f2008_parser") def test_handling_intrinsics_named_args( - code, expected_type, expected_op, expected_names, symbol_table): + code, expected_intrinsic, expected_names, symbol_table): '''Test that the fparser2 _intrinsic_handler method deals with Intrinsic_Function_Reference nodes that are translated to PSyIR Operation nodes and have named arguments. @@ -336,9 +345,9 @@ def test_handling_intrinsics_named_args( processor.process_nodes(fake_parent, [fp2node]) assign = fake_parent.children[0] assert isinstance(assign, Assignment) - assert isinstance(assign.rhs, expected_type), \ + assert isinstance(assign.rhs, IntrinsicCall), \ "Fails when parsing '" + code + "'" - assert assign.rhs._operator == expected_op, \ + assert assign.rhs._intrinsic == expected_intrinsic, \ "Fails when parsing '" + code + "'" assert len(assign.rhs.children) == len(assign.rhs._argument_names) for idx, child in enumerate(assign.rhs.children): @@ -348,85 +357,17 @@ def test_handling_intrinsics_named_args( @pytest.mark.usefixtures("f2008_parser") -def test_intrinsic_no_args(): - ''' Check that an intrinsic with no arguments results in a - NotImplementedError. ''' +def test_intrinsic_no_args(symbol_table): + ''' Check that an intrinsic with no arguments is parsed correctly. ''' processor = Fparser2Reader() - fake_parent = Schedule() - reader = FortranStringReader("x = MAX(a, b)") - fp2node = Execution_Part.match(reader)[0][0].items[2] - # Manually remove the arguments - fp2node.items = (fp2node.items[0],) - with pytest.raises(NotImplementedError) as err: - processor._intrinsic_handler(fp2node, fake_parent) - assert ("Operator 'MAX' has no arguments but operators must have at " - "least one." in str(err.value)) - - -@pytest.mark.usefixtures("f2008_parser") -def test_unary_op_handler_error(): - ''' Check that the unary op handler raises the expected error if the - parse tree has an unexpected structure. This is a hard error to - provoke since fparser checks that the number of arguments is correct. ''' - processor = Fparser2Reader() - fake_parent = Schedule() - reader = FortranStringReader("x = exp(a)") - fp2node = Execution_Part.match(reader)[0][0].items[2] - # Create an fparser node for a binary operation so that we can steal - # its operands - reader = FortranStringReader("x = max(a, b)") - maxnode = Execution_Part.match(reader)[0][0].items[2] - # Break the number of arguments in the fparser node by using those - # from the binary operation - fp2node.items = (fp2node.items[0], maxnode.items[1]) - with pytest.raises(InternalError) as err: - processor._unary_op_handler(fp2node, fake_parent) - assert ("Operation 'EXP(a, b)' has more than one argument and is " - "therefore not unary" in str(err.value)) - - -@pytest.mark.usefixtures("f2008_parser") -def test_binary_op_handler_error(): - ''' Check that the binary op handler raises the expected errors if the - parse tree has an unexpected structure. ''' - processor = Fparser2Reader() - fake_parent = Schedule() - reader = FortranStringReader("x = MAX(a, b)") - fp2node = Execution_Part.match(reader)[0][0].items[2] - # Break the number of arguments in the fparser node - fp2node.items[1].items = (Name('a'),) - with pytest.raises(InternalError) as err: - processor._binary_op_handler(fp2node, fake_parent) - assert ("Binary operator should have exactly two arguments but found 1 " - "for 'MAX(a)'." in str(err.value)) - # Now break the 'items' tuple of this fparser node - fp2node.items = (fp2node.items[0], Name('dummy')) - with pytest.raises(InternalError) as err: - processor._binary_op_handler(fp2node, fake_parent) - assert ("binary intrinsic operation 'MAX(dummy)'. Expected second child " - "to be Actual_Arg_Spec_List" in str(err.value)) - - -@pytest.mark.usefixtures("f2008_parser") -def test_nary_op_handler_error(): - ''' Check that the Nary op handler raises the expected error if the parse - tree has an unexpected structure. ''' - processor = Fparser2Reader() - fake_parent = Schedule() - reader = FortranStringReader("x = MAX(a, b, c)") - fp2node = Execution_Part.match(reader)[0][0].items[2] - # Give the node an incorrect number of arguments for the Nary handler - fp2node.items[1].items = (Name('a'),) - with pytest.raises(InternalError) as err: - processor._nary_op_handler(fp2node, fake_parent) - assert ("An N-ary operation must have more than two arguments but found 1 " - "for 'MAX(a)'" in str(err.value)) - # Break the 'items' tuple of this fparser node - fp2node.items = (fp2node.items[0], Name('dummy')) - with pytest.raises(InternalError) as err: - processor._nary_op_handler(fp2node, fake_parent) - assert ("Expected second 'item' of N-ary intrinsic 'MAX(dummy)' in fparser" - " parse tree to be an Actual_Arg_Spec_List" in str(err.value)) + fake_parent = Schedule(symbol_table=symbol_table) + reader = FortranStringReader("x = NULL()") + fp2node = Execution_Part.match(reader)[0][0] + processor.process_nodes(fake_parent, [fp2node]) + assign = fake_parent.children[0] + assert isinstance(assign.rhs, IntrinsicCall) + assert assign.rhs.intrinsic == IntrinsicCall.Intrinsic.NULL + assert len(assign.rhs.children) == 0 @pytest.mark.usefixtures("f2008_parser") diff --git a/src/psyclone/tests/psyir/frontend/fparser2_nint_intrinsic_test.py b/src/psyclone/tests/psyir/frontend/fparser2_nint_intrinsic_test.py index f0be0a4f13..22dcb213ca 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_nint_intrinsic_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_nint_intrinsic_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -31,7 +31,8 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -# Author A. R. Porter, STFC Daresbury Laboratory +# Author: A. R. Porter, STFC Daresbury Laboratory +# Modified: S. Siso, STFC Daresbury Laboratory ''' Module containing pytest tests for the handling of the NINT intrinsic in the PSyIR. ''' @@ -39,7 +40,7 @@ from fparser.common.readfortran import FortranStringReader from psyclone.psyir.frontend.fparser2 import Fparser2Reader -from psyclone.psyir.nodes import (Assignment, UnaryOperation, +from psyclone.psyir.nodes import (Assignment, IntrinsicCall, BinaryOperation, Routine) TEST_CODE = ''' @@ -65,10 +66,10 @@ def test_nint(parser): psyir = processor.generate_psyir(ptree) sched = psyir.walk(Routine)[0] assert isinstance(sched[0], Assignment) - assert isinstance(sched[0].rhs, UnaryOperation) - assert sched[0].rhs.operator == UnaryOperation.Operator.NINT + assert isinstance(sched[0].rhs, IntrinsicCall) + assert sched[0].rhs.intrinsic == IntrinsicCall.Intrinsic.NINT assert isinstance(sched[0].rhs.children[0], BinaryOperation) assert isinstance(sched[1], Assignment) assert isinstance(sched[1].rhs, BinaryOperation) - assert isinstance(sched[1].rhs.children[1], UnaryOperation) - assert sched[1].rhs.children[1].operator == UnaryOperation.Operator.NINT + assert isinstance(sched[1].rhs.children[1], IntrinsicCall) + assert sched[1].rhs.children[1].intrinsic == IntrinsicCall.Intrinsic.NINT diff --git a/src/psyclone/tests/psyir/frontend/fparser2_size_intrinsic_test.py b/src/psyclone/tests/psyir/frontend/fparser2_size_intrinsic_test.py index cc0cefeb5f..443cd6ea95 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_size_intrinsic_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_size_intrinsic_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2019-2020, Science and Technology Facilities Council. +# Copyright (c) 2019-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,15 +32,17 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author A. R. Porter, STFC Daresbury Laboratory +# Modified: S. Siso, STFC Daresbury Laboratory ''' Module containing pytest tests for the handling of the SIZE intrinsic in the PSyIR. ''' -from __future__ import absolute_import - import pytest from fparser.common.readfortran import FortranStringReader +from fparser.two.Fortran2003 import Execution_Part from psyclone.psyir.frontend.fparser2 import Fparser2Reader +from psyclone.psyir.nodes import Schedule, Assignment, IntrinsicCall, \ + Reference, Literal @pytest.mark.parametrize("expression", ["n = SIZE(a, 3)", @@ -53,15 +55,13 @@ def test_size(expression): TODO #754 fix test so that 'disable_declaration_check' fixture is not required. ''' - from fparser.two.Fortran2003 import Execution_Part - from psyclone.psyir.nodes import Schedule, Assignment, BinaryOperation, \ - Reference, Literal fake_parent = Schedule() processor = Fparser2Reader() reader = FortranStringReader(expression) fp2intrinsic = Execution_Part(reader).content[0] processor.process_nodes(fake_parent, [fp2intrinsic]) assert isinstance(fake_parent[0], Assignment) - assert isinstance(fake_parent[0].rhs, BinaryOperation) + assert isinstance(fake_parent[0].rhs, IntrinsicCall) + assert fake_parent[0].rhs.intrinsic == IntrinsicCall.Intrinsic.SIZE assert isinstance(fake_parent[0].rhs.children[0], Reference) assert isinstance(fake_parent[0].rhs.children[1], Literal) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index 25674aa232..3004257b4d 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -65,8 +65,7 @@ DataSymbol, ContainerSymbol, SymbolTable, ArgumentInterface, SymbolError, ScalarType, ArrayType, INTEGER_TYPE, REAL_TYPE, UnknownFortranType, DeferredType, Symbol, UnresolvedInterface, - ImportInterface, BOOLEAN_TYPE, StaticInterface, UnknownInterface, - AutomaticInterface, DefaultModuleInterface) + ImportInterface, BOOLEAN_TYPE, StaticInterface, UnknownInterface) # pylint: disable=too-many-statements @@ -157,72 +156,73 @@ def test_is_bound_full_extent(): with pytest.raises(TypeError) as excinfo: _is_bound_full_extent(array_reference, 1, None) - assert ("'operator' argument expected to be LBOUND or UBOUND but found " + assert ("'intrinsic' argument expected to be LBOUND or UBOUND but found " "'NoneType'" in str(excinfo.value)) # Expecting BinaryOperation but found Literal assert not _is_bound_full_extent(array_reference, 1, - BinaryOperation.Operator.UBOUND) + IntrinsicCall.Intrinsic.UBOUND) - operator = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, one.copy(), one.copy()) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [one.copy(), ("dim", one.copy())]) my_range = Range.create(operator, one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) - # Expecting operator to be Operator.LBOUND, but found - # Operator.UBOUND + # Expecting intrinsic to be LBOUND, but found UBOUND assert not _is_bound_full_extent(array_reference, 1, - BinaryOperation.Operator.LBOUND) + IntrinsicCall.Intrinsic.LBOUND) - operator = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, one.copy(), one.copy()) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [one.copy(), ("dim", one.copy())]) my_range = Range.create(operator, one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) # Expecting Reference but found Literal assert not _is_bound_full_extent(array_reference, 1, - BinaryOperation.Operator.LBOUND) + IntrinsicCall.Intrinsic.LBOUND) - operator = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(DataSymbol("x", INTEGER_TYPE)), one.copy()) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(DataSymbol("x", INTEGER_TYPE)), ("dim", one.copy())]) my_range = Range.create(operator, one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) # Expecting Reference symbol x to be the same as array symbol a assert not _is_bound_full_extent(array_reference, 1, - BinaryOperation.Operator.LBOUND) + IntrinsicCall.Intrinsic.LBOUND) - operator = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol), Literal("1.0", REAL_TYPE)) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), ("dim", Literal("1.0", REAL_TYPE))]) my_range = Range.create(operator, one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) # Expecting integer but found real assert not _is_bound_full_extent(array_reference, 1, - BinaryOperation.Operator.LBOUND) + IntrinsicCall.Intrinsic.LBOUND) - operator = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol), Literal("2", INTEGER_TYPE)) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), ("dim", Literal("2", INTEGER_TYPE))]) my_range = Range.create(operator, one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) # Expecting literal value 2 to be the same as the current array # dimension 1 assert not _is_bound_full_extent(array_reference, 1, - BinaryOperation.Operator.LBOUND) + IntrinsicCall.Intrinsic.LBOUND) - operator = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol), Literal("1", INTEGER_TYPE)) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), ("dim", Literal("1", INTEGER_TYPE))]) my_range = Range.create(operator, one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) # valid assert _is_bound_full_extent(array_reference, 1, - BinaryOperation.Operator.LBOUND) + IntrinsicCall.Intrinsic.LBOUND) def test_is_array_range_literal(): @@ -238,9 +238,9 @@ def test_is_array_range_literal(): one = Literal("1", INTEGER_TYPE) array_type = ArrayType(REAL_TYPE, [20]) symbol = DataSymbol('a', array_type) - operator = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol), Literal("1", INTEGER_TYPE)) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), ("dim", Literal("1", INTEGER_TYPE))]) my_range = Range.create(operator, one) array_reference = ArrayReference.create(symbol, [my_range]) @@ -294,12 +294,12 @@ def test_is_range_full_extent(): one = Literal("1", INTEGER_TYPE) array_type = ArrayType(REAL_TYPE, [2]) symbol = DataSymbol('a', array_type) - lbound_op = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol), Literal("1", INTEGER_TYPE)) - ubound_op = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(symbol), Literal("1", INTEGER_TYPE)) + lbound_op = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), ("dim", Literal("1", INTEGER_TYPE))]) + ubound_op = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(symbol), ("dim", Literal("1", INTEGER_TYPE))]) my_range = Range.create(lbound_op, ubound_op, one) _ = ArrayReference.create(symbol, [my_range]) @@ -394,12 +394,14 @@ def test_array_notation_rank(): assert "No array access found in node 'field'" in str(err.value) # Structure reference with ranges in more than one part reference. - lbound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - StructureReference.create(symbol, ["first"]), int_one.copy()) - ubound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - StructureReference.create(symbol, ["first"]), int_one.copy()) + lbound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [StructureReference.create(symbol, ["first"]), + ("dim", int_one.copy())]) + ubound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [StructureReference.create(symbol, ["first"]), + ("dim", int_one.copy())]) my_range = Range.create(lbound, ubound) with pytest.raises(InternalError) as err: Fparser2Reader._array_notation_rank( @@ -407,8 +409,8 @@ def test_array_notation_rank(): ("second", [my_range.copy()])])) assert ("Found a structure reference containing two or more part " "references that have ranges: 'field%first(:)%second(" - "LBOUND(field%first, 1):UBOUND(field%first, 1))'. This is not " - "valid within a WHERE in Fortran." in str(err.value)) + "LBOUND(field%first, dim=1):UBOUND(field%first, dim=1))'. This is " + "not valid within a WHERE in Fortran." in str(err.value)) # Repeat but this time for an ArrayOfStructuresReference. with pytest.raises(InternalError) as err: Fparser2Reader._array_notation_rank( @@ -416,10 +418,10 @@ def test_array_notation_rank(): ["first", ("second", [my_range.copy()])])) assert ("Found a structure reference containing two or more part " - "references that have ranges: 'field(LBOUND(field%first, 1):" - "UBOUND(field%first, 1))%first%second(" - "LBOUND(field%first, 1):UBOUND(field%first, 1))'. This is not " - "valid within a WHERE in Fortran." in str(err.value)) + "references that have ranges: 'field(LBOUND(field%first, dim=1):" + "UBOUND(field%first, dim=1))%first%second(" + "LBOUND(field%first, dim=1):UBOUND(field%first, dim=1))'. This is " + "not valid within a WHERE in Fortran." in str(err.value)) # An array with no dimensions raises an exception array_type = ArrayType(REAL_TYPE, [10]) @@ -435,18 +437,18 @@ def test_array_notation_rank(): # in that dimension array_type = ArrayType(REAL_TYPE, [10, 10, 10]) symbol = DataSymbol("a", array_type) - lbound_op1 = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol), Literal("1", INTEGER_TYPE)) - ubound_op1 = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(symbol), Literal("1", INTEGER_TYPE)) - lbound_op3 = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(symbol), Literal("3", INTEGER_TYPE)) - ubound_op3 = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(symbol), Literal("3", INTEGER_TYPE)) + lbound_op1 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), ("dim", Literal("1", INTEGER_TYPE))]) + ubound_op1 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(symbol), ("dim", Literal("1", INTEGER_TYPE))]) + lbound_op3 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(symbol), ("dim", Literal("3", INTEGER_TYPE))]) + ubound_op3 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(symbol), ("dim", Literal("3", INTEGER_TYPE))]) range1 = Range.create(lbound_op1, ubound_op1) range2 = Range.create(lbound_op3, ubound_op3) @@ -705,6 +707,7 @@ def test_get_partial_datatype(): datatype, init = processor._get_partial_datatype(node, fake_parent, {}) assert isinstance(datatype, ScalarType) assert isinstance(init, Literal) + assert init.parent is None assert datatype.intrinsic is ScalarType.Intrinsic.INTEGER # Check fparser2 tree is unmodified assert ids == [id(entry) for entry in walk(node)] @@ -717,6 +720,7 @@ def test_get_partial_datatype(): datatype, init = processor._get_partial_datatype(node, fake_parent, {}) assert isinstance(datatype, ScalarType) assert isinstance(init, CodeBlock) + assert init.parent is None assert datatype.intrinsic is ScalarType.Intrinsic.INTEGER # Check fparser2 tree is unmodified assert ids == [id(entry) for entry in walk(node)] @@ -757,6 +761,7 @@ def test_get_partial_datatype(): datatype, init = processor._get_partial_datatype(node, fake_parent, {}) assert isinstance(datatype, ScalarType) assert isinstance(init, CodeBlock) + assert init.parent is None assert datatype.intrinsic is ScalarType.Intrinsic.INTEGER # Check fparser2 tree is unmodified assert ids == [id(entry) for entry in walk(node)] @@ -1117,13 +1122,14 @@ def test_process_unsupported_declarations(fortran_reader): assert isinstance(psyir.children[0].symbol_table.lookup("l").datatype, UnknownFortranType) - # Unsupported initialisation of a parameter which comes after a valid - # initialisation and is then followed by another, valid initialisation - # which references the second one. + # Test that CodeBlocks and refernces to variables initialised with a + # CodeBlock are handled correctly reader = FortranStringReader( - "INTEGER, PARAMETER :: happy=1, fbsp=SELECTED_REAL_KIND(6,37), " + "INTEGER, PARAMETER :: happy=1, fbsp=sin(1), " " sad=fbsp") fparser2spec = Specification_Part(reader).content[0] + # We change SIN for something that creates a CodeBlock + fparser2spec.items[2].items[1].items[3].items[1].items[0].string = "CBLOCK" processor.process_declarations(fake_parent, [fparser2spec], []) fbsym = fake_parent.symbol_table.lookup("fbsp") assert fbsym.datatype.intrinsic == ScalarType.Intrinsic.INTEGER @@ -2160,9 +2166,9 @@ def _check_reference(node, dim, index, name): _check_array(array_reference, ndims=1) _check_range(array_reference, dim=1) assert _is_bound_full_extent(array_reference, 1, - BinaryOperation.Operator.LBOUND) + IntrinsicCall.Intrinsic.LBOUND) assert _is_bound_full_extent(array_reference, 1, - BinaryOperation.Operator.UBOUND) + IntrinsicCall.Intrinsic.UBOUND) assert _is_array_range_literal( array_reference, dim=1, index=2, value=1) # Simple multi-dimensional @@ -2174,10 +2180,10 @@ def _check_reference(node, dim, index, name): _check_range(array_reference, dim=dim) assert _is_bound_full_extent( array_reference, dim, - BinaryOperation.Operator.LBOUND) + IntrinsicCall.Intrinsic.LBOUND) assert _is_bound_full_extent( array_reference, dim, - BinaryOperation.Operator.UBOUND) + IntrinsicCall.Intrinsic.UBOUND) assert _is_array_range_literal( array_reference, dim=dim, index=2, value=1) # Simple values @@ -2188,7 +2194,7 @@ def _check_reference(node, dim, index, name): _check_range(array_reference, dim=1) assert _is_array_range_literal(array_reference, dim=1, index=0, value=1) assert _is_bound_full_extent(array_reference, 1, - BinaryOperation.Operator.UBOUND) + IntrinsicCall.Intrinsic.UBOUND) assert _is_array_range_literal(array_reference, dim=1, index=2, value=1) # dim 2 _check_range(array_reference, dim=2) @@ -2203,27 +2209,27 @@ def _check_reference(node, dim, index, name): # dim 4 _check_range(array_reference, dim=4) assert _is_bound_full_extent(array_reference, 4, - BinaryOperation.Operator.LBOUND) + IntrinsicCall.Intrinsic.LBOUND) assert _is_array_range_literal(array_reference, dim=4, index=1, value=2) assert _is_array_range_literal(array_reference, dim=4, index=2, value=1) # dim 5 _check_range(array_reference, dim=5) assert _is_bound_full_extent(array_reference, 5, - BinaryOperation.Operator.LBOUND) + IntrinsicCall.Intrinsic.LBOUND) assert _is_array_range_literal(array_reference, dim=5, index=1, value=2) assert _is_array_range_literal(array_reference, dim=5, index=2, value=3) # dim 6 _check_range(array_reference, dim=6) assert _is_bound_full_extent(array_reference, 6, - BinaryOperation.Operator.LBOUND) + IntrinsicCall.Intrinsic.LBOUND) assert _is_bound_full_extent(array_reference, 6, - BinaryOperation.Operator.UBOUND) + IntrinsicCall.Intrinsic.UBOUND) assert _is_array_range_literal(array_reference, dim=6, index=2, value=3) # dim 7 _check_range(array_reference, dim=7) assert _is_array_range_literal(array_reference, dim=7, index=0, value=1) assert _is_bound_full_extent(array_reference, 7, - BinaryOperation.Operator.UBOUND) + IntrinsicCall.Intrinsic.UBOUND) assert _is_array_range_literal(array_reference, dim=7, index=2, value=3) # Simple variables @@ -2234,7 +2240,7 @@ def _check_reference(node, dim, index, name): _check_range(array_reference, dim=1) _check_reference(array_reference, dim=1, index=0, name="b") assert _is_bound_full_extent(array_reference, 1, - BinaryOperation.Operator.UBOUND) + IntrinsicCall.Intrinsic.UBOUND) assert _is_array_range_literal(array_reference, dim=1, index=2, value=1) # dim 2 _check_range(array_reference, dim=2) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index 751fc48e55..23df201079 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2019-2022, Science and Technology Facilities Council. +# Copyright (c) 2019-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -46,7 +46,8 @@ from psyclone.psyir.frontend.fparser2 import Fparser2Reader from psyclone.psyir.nodes import Schedule, CodeBlock, Loop, ArrayReference, \ Assignment, Literal, Reference, UnaryOperation, BinaryOperation, IfBlock, \ - Call, Routine, Container, Range, ArrayMember, StructureReference + Call, Routine, Container, Range, ArrayMember, StructureReference,\ + IntrinsicCall from psyclone.psyir.symbols import DataSymbol, ArrayType, ScalarType, \ REAL_TYPE, INTEGER_TYPE, UnresolvedInterface @@ -341,7 +342,8 @@ def test_basic_where(): assert isinstance(loop.ast, Fortran2003.Where_Construct) assert isinstance(loops[0].children[0], Literal) - assert isinstance(loops[0].children[1], BinaryOperation) + assert isinstance(loops[0].children[1], IntrinsicCall) + assert loops[0].children[1].intrinsic == IntrinsicCall.Intrinsic.SIZE assert str(loops[0].children[1].children[0]) == "Reference[name:'dry']" ifblock = loops[2].loop_body[0] @@ -579,7 +581,8 @@ def test_where_derived_type(fortran_reader, fortran_writer, code, size_arg): psyir = fortran_reader.psyir_from_source(code) loops = psyir.walk(Loop) assert len(loops) == 2 - assert isinstance(loops[1].stop_expr, BinaryOperation) + assert isinstance(loops[1].stop_expr, IntrinsicCall) + assert loops[1].stop_expr.intrinsic == IntrinsicCall.Intrinsic.SIZE assert isinstance(loops[1].stop_expr.children[0], StructureReference) assert fortran_writer(loops[1].stop_expr.children[0]) == size_arg assert isinstance(loops[1].loop_body[0], IfBlock) diff --git a/src/psyclone/tests/psyir/nodes/array_member_test.py b/src/psyclone/tests/psyir/nodes/array_member_test.py index 703879cff2..654f0b6dd5 100644 --- a/src/psyclone/tests/psyir/nodes/array_member_test.py +++ b/src/psyclone/tests/psyir/nodes/array_member_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,11 +32,11 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: A. R. Porter, STFC Daresbury Lab +# Modified: S. Siso, STFC Daresbury Lab # ----------------------------------------------------------------------------- ''' This module contains pytest tests for the ArrayMember class. ''' -from __future__ import absolute_import import pytest from psyclone.psyir import symbols, nodes from psyclone.errors import GenerationError @@ -90,48 +90,48 @@ def test_am_is_lower_upper_bound(): sym = symbols.DataSymbol("grid_var", grid_type) ref = nodes.StructureReference.create(sym, ["data"]) # Start and stop for the range are binary operators but not the right ones - start = nodes.operation.BinaryOperation.create( - nodes.operation.BinaryOperation.Operator.UBOUND, - ref.copy(), one.copy()) - stop = nodes.operation.BinaryOperation.create( - nodes.operation.BinaryOperation.Operator.LBOUND, - ref.copy(), one.copy()) + start = nodes.IntrinsicCall.create( + nodes.IntrinsicCall.Intrinsic.UBOUND, + [ref.copy(), ("dim", one.copy())]) + stop = nodes.IntrinsicCall.create( + nodes.IntrinsicCall.Intrinsic.LBOUND, + [ref.copy(), ("dim", one.copy())]) my_range = nodes.Range.create(start, stop) sref = nodes.StructureReference.create(sym, [("data", [my_range])]) amem2 = sref.walk(nodes.ArrayMember)[0] assert amem2.is_lower_bound(0) is False assert amem2.is_upper_bound(0) is False # Correct binary operators but wrong types of operand - start = nodes.operation.BinaryOperation.create( - nodes.operation.BinaryOperation.Operator.LBOUND, - one.copy(), one.copy()) - stop = nodes.operation.BinaryOperation.create( - nodes.operation.BinaryOperation.Operator.UBOUND, - one.copy(), one.copy()) + start = nodes.IntrinsicCall.create( + nodes.IntrinsicCall.Intrinsic.LBOUND, + [one.copy(), ("dim", one.copy())]) + stop = nodes.IntrinsicCall.create( + nodes.IntrinsicCall.Intrinsic.UBOUND, + [one.copy(), ("dim", one.copy())]) my_range = nodes.Range.create(start, stop) sref = nodes.StructureReference.create(sym, [("data", [my_range])]) amem2 = sref.walk(nodes.ArrayMember)[0] assert amem2.is_lower_bound(0) is False assert amem2.is_upper_bound(0) is False # Correct start and stop arguments to Range - start = nodes.operation.BinaryOperation.create( - nodes.operation.BinaryOperation.Operator.LBOUND, - ref.copy(), one.copy()) - stop = nodes.operation.BinaryOperation.create( - nodes.operation.BinaryOperation.Operator.UBOUND, - ref.copy(), one.copy()) + start = nodes.IntrinsicCall.create( + nodes.IntrinsicCall.Intrinsic.LBOUND, + [ref.copy(), ("dim", one.copy())]) + stop = nodes.IntrinsicCall.create( + nodes.IntrinsicCall.Intrinsic.UBOUND, + [ref.copy(), ("dim", one.copy())]) my_range = nodes.Range.create(start, stop) sref = nodes.StructureReference.create(sym, [("data", [my_range])]) amem2 = sref.walk(nodes.ArrayMember)[0] assert amem2.is_lower_bound(0) is True assert amem2.is_upper_bound(0) is True # Range in a dimension other than the first - start = nodes.operation.BinaryOperation.create( - nodes.operation.BinaryOperation.Operator.LBOUND, - ref.copy(), two.copy()) - stop = nodes.operation.BinaryOperation.create( - nodes.operation.BinaryOperation.Operator.UBOUND, - ref.copy(), two.copy()) + start = nodes.IntrinsicCall.create( + nodes.IntrinsicCall.Intrinsic.LBOUND, + [ref.copy(), ("dim", two.copy())]) + stop = nodes.IntrinsicCall.create( + nodes.IntrinsicCall.Intrinsic.UBOUND, + [ref.copy(), ("dim", two.copy())]) my_range = nodes.Range.create(start, stop) sref = nodes.StructureReference.create(sym, [("data", [one.copy(), my_range])]) diff --git a/src/psyclone/tests/psyir/nodes/array_mixin_test.py b/src/psyclone/tests/psyir/nodes/array_mixin_test.py index 99aa644967..41a1e43a0b 100644 --- a/src/psyclone/tests/psyir/nodes/array_mixin_test.py +++ b/src/psyclone/tests/psyir/nodes/array_mixin_test.py @@ -42,7 +42,7 @@ from psyclone.errors import InternalError from psyclone.psyir.nodes import ( ArrayOfStructuresReference, ArrayReference, BinaryOperation, Range, - Literal, Routine, StructureReference, Assignment, Reference) + Literal, Routine, StructureReference, Assignment, Reference, IntrinsicCall) from psyclone.psyir.symbols import ( ArrayType, DataSymbol, DataTypeSymbol, DeferredType, INTEGER_TYPE, REAL_TYPE, StructureType, Symbol) @@ -63,41 +63,44 @@ def test_is_bound_op(): array = DataSymbol("my_symbol", ArrayType(INTEGER_TYPE, [10])) array2 = DataSymbol("my_symbol2", ArrayType(INTEGER_TYPE, [10])) scalar = DataSymbol("tmp", INTEGER_TYPE) - ubound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, Reference(array), _ONE.copy()) + ubound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(array), ("dim", _ONE.copy())]) my_range = Range.create(_ONE.copy(), ubound) array_ref = ArrayReference.create(array, [my_range]) array2_ref = ArrayReference.create(array2, [my_range.copy()]) # not a binary operation oper = None assert not array_ref._is_bound_op( - oper, BinaryOperation.Operator.UBOUND, _ONE) + oper, IntrinsicCall.Intrinsic.UBOUND, _ONE) # not a match with the binary operator - oper = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, array_ref, _ONE.copy()) - assert not array_ref._is_bound_op(oper, BinaryOperation.Operator.UBOUND, 0) + oper = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, [array_ref, ("dim", _ONE.copy())]) + assert not array_ref._is_bound_op(oper, IntrinsicCall.Intrinsic.UBOUND, 0) # 1st dimension of the bound is not the same array - oper = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, array2_ref, _ONE.copy()) - assert not array_ref._is_bound_op(oper, BinaryOperation.Operator.UBOUND, 0) + oper = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, [array2_ref, ("dim", _ONE.copy())]) + assert not array_ref._is_bound_op(oper, IntrinsicCall.Intrinsic.UBOUND, 0) # 2nd dimension of the bound not a literal - oper = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, array_ref.copy(), Reference(scalar)) - assert not array_ref._is_bound_op(oper, BinaryOperation.Operator.UBOUND, 0) + oper = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [array_ref.copy(), ("dim", Reference(scalar))]) + assert not array_ref._is_bound_op(oper, IntrinsicCall.Intrinsic.UBOUND, 0) # 2nd dimension of the bound not an integer literal - oper = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, array_ref.copy(), - Literal("1.0", REAL_TYPE)) - assert not array_ref._is_bound_op(oper, BinaryOperation.Operator.UBOUND, 0) + oper = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [array_ref.copy(), ("dim", Literal("1.0", REAL_TYPE))]) + assert not array_ref._is_bound_op(oper, IntrinsicCall.Intrinsic.UBOUND, 0) # 2nd dimension of the bound not the expected index - oper = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, array_ref.copy(), - Literal("2", INTEGER_TYPE)) - assert not array_ref._is_bound_op(oper, BinaryOperation.Operator.UBOUND, 0) + oper = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [array_ref.copy(), ("dim", Literal("2", INTEGER_TYPE))]) + assert not array_ref._is_bound_op(oper, IntrinsicCall.Intrinsic.UBOUND, 0) # OK - oper = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, array_ref.copy(), _ONE.copy()) - assert array_ref._is_bound_op(oper, BinaryOperation.Operator.UBOUND, 0) + oper = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [array_ref.copy(), ("dim", _ONE.copy())]) + assert array_ref._is_bound_op(oper, IntrinsicCall.Intrinsic.UBOUND, 0) # is_lower_bound and is_upper_bound @@ -312,8 +315,8 @@ def test_get_bound_expression(): dtref = ArrayReference.create(dtsym, [_ONE.copy(), _ONE.copy(), _ONE.copy()]) lbnd = dtref._get_bound_expression(1, "lower") - assert isinstance(lbnd, BinaryOperation) - assert lbnd.operator == BinaryOperation.Operator.LBOUND + assert isinstance(lbnd, IntrinsicCall) + assert lbnd.intrinsic == IntrinsicCall.Intrinsic.LBOUND assert lbnd.children[0].symbol is dtsym assert lbnd.children[1] == Literal("2", INTEGER_TYPE) @@ -341,8 +344,8 @@ def test_get_bound_expression(): dtref = ArrayReference.create(dtsym, [_ONE.copy(), _ONE.copy(), _ONE.copy()]) ubnd = dtref._get_bound_expression(1, "upper") - assert isinstance(ubnd, BinaryOperation) - assert ubnd.operator == BinaryOperation.Operator.UBOUND + assert isinstance(ubnd, IntrinsicCall) + assert ubnd.intrinsic == IntrinsicCall.Intrinsic.UBOUND assert ubnd.children[0].symbol is dtsym assert ubnd.children[1] == Literal("2", INTEGER_TYPE) @@ -359,13 +362,13 @@ def test_get_bound_expression_unknown_size(extent): [extent, extent])) aref = ArrayReference.create(symbol, [_ONE.copy(), _ONE.copy()]) lbnd = aref._get_bound_expression(1, "lower") - assert isinstance(lbnd, BinaryOperation) - assert lbnd.operator == BinaryOperation.Operator.LBOUND + assert isinstance(lbnd, IntrinsicCall) + assert lbnd.intrinsic == IntrinsicCall.Intrinsic.LBOUND assert lbnd.children[0].symbol is symbol ubnd = aref._get_bound_expression(1, "upper") - assert isinstance(ubnd, BinaryOperation) - assert ubnd.operator == BinaryOperation.Operator.UBOUND + assert isinstance(ubnd, IntrinsicCall) + assert ubnd.intrinsic == IntrinsicCall.Intrinsic.UBOUND assert ubnd.children[0].symbol is symbol @@ -379,10 +382,10 @@ def test_aref_to_aos_bound_expression(): [("ID", INTEGER_TYPE, Symbol.Visibility.PUBLIC)]) sgrid_type_sym = DataTypeSymbol("subgrid_type", sgrid_type) sym = DataSymbol("subgrids", ArrayType(sgrid_type_sym, [(3, 10)])) - lbound = BinaryOperation.create(BinaryOperation.Operator.LBOUND, - Reference(sym), _ONE.copy()) - ubound = BinaryOperation.create(BinaryOperation.Operator.UBOUND, - Reference(sym), _ONE.copy()) + lbound = IntrinsicCall.create(IntrinsicCall.Intrinsic.LBOUND, + [Reference(sym), ("dim", _ONE.copy())]) + ubound = IntrinsicCall.create(IntrinsicCall.Intrinsic.UBOUND, + [Reference(sym), ("dim", _ONE.copy())]) array = ArrayReference.create(sym, [Range.create(lbound, ubound)]) lbnd = array._get_bound_expression(0, "lower") assert lbnd.value == "3" @@ -408,23 +411,23 @@ def test_member_get_bound_expression(fortran_writer): assert ("'bound' argument must be 'lower' or 'upper. " "Found 'notvalid'" in str(excinfo.value)) lbnd = ref.member._get_bound_expression(0, "lower") - assert isinstance(lbnd, BinaryOperation) + assert isinstance(lbnd, IntrinsicCall) out = fortran_writer(lbnd).lower() - assert out == "lbound(grid_var%data, 1)" + assert "lbound(grid_var%data, dim=1)" in out usym = DataSymbol("uvar", DeferredType()) ref = ArrayOfStructuresReference.create( usym, [_ONE.copy()], [("map", [_ONE.copy(), _TWO.copy()]), ("data", [_ONE.copy()])]) lbnd = ref.member.member._get_bound_expression(0, "lower") - assert isinstance(lbnd, BinaryOperation) + assert isinstance(lbnd, IntrinsicCall) out = fortran_writer(lbnd).lower() - assert out == "lbound(uvar(1)%map(1,2)%data, 1)" + assert "lbound(uvar(1)%map(1,2)%data, dim=1)" in out ubnd = ref.member._get_bound_expression(0, "upper") - assert isinstance(ubnd, BinaryOperation) + assert isinstance(ubnd, IntrinsicCall) out = fortran_writer(ubnd).lower() - assert out == "ubound(uvar(1)%map, 1)" + assert "ubound(uvar(1)%map, dim=1)" in out # Second, test when we do have type information. a2d = ArrayType(REAL_TYPE, [2, (2, 8)]) # Structure that contains "map" which is a 2D array. diff --git a/src/psyclone/tests/psyir/nodes/array_of_structures_reference_test.py b/src/psyclone/tests/psyir/nodes/array_of_structures_reference_test.py index 520a3d78ff..436d921e43 100644 --- a/src/psyclone/tests/psyir/nodes/array_of_structures_reference_test.py +++ b/src/psyclone/tests/psyir/nodes/array_of_structures_reference_test.py @@ -31,7 +31,7 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -# Authors: A. R. Porter and N. Nobre, STFC Daresbury Lab +# Authors: A. R. Porter, N. Nobre and S. Siso, STFC Daresbury Lab # J. Henrichs, Bureau of Meteorology # ----------------------------------------------------------------------------- @@ -92,12 +92,12 @@ def test_asr_create(component_symbol): assert isinstance(asref.children[0], nodes.StructureMember) assert isinstance(asref.children[0].children[0], nodes.Member) # Reference to range of structures - lbound = nodes.BinaryOperation.create( - nodes.BinaryOperation.Operator.LBOUND, - nodes.Reference(component_symbol), int_one.copy()) - ubound = nodes.BinaryOperation.create( - nodes.BinaryOperation.Operator.UBOUND, - nodes.Reference(component_symbol), int_one.copy()) + lbound = nodes.IntrinsicCall.create( + nodes.IntrinsicCall.Intrinsic.LBOUND, + [nodes.Reference(component_symbol), ("dim", int_one.copy())]) + ubound = nodes.IntrinsicCall.create( + nodes.IntrinsicCall.Intrinsic.UBOUND, + [nodes.Reference(component_symbol), ("dim", int_one.copy())]) my_range = nodes.Range.create(lbound, ubound) asref = nodes.ArrayOfStructuresReference.create(component_symbol, [my_range], ["nx"]) @@ -107,12 +107,12 @@ def test_asr_create(component_symbol): check_links(asref.children[1], asref.children[1].children) # Test to enforce a type: - lbound = nodes.BinaryOperation.create( - nodes.BinaryOperation.Operator.LBOUND, - nodes.Reference(component_symbol), int_one.copy()) - ubound = nodes.BinaryOperation.create( - nodes.BinaryOperation.Operator.UBOUND, - nodes.Reference(component_symbol), int_one.copy()) + lbound = nodes.IntrinsicCall.create( + nodes.IntrinsicCall.Intrinsic.LBOUND, + [nodes.Reference(component_symbol), ("dim", int_one.copy())]) + ubound = nodes.IntrinsicCall.create( + nodes.IntrinsicCall.Intrinsic.UBOUND, + [nodes.Reference(component_symbol), ("dim", int_one.copy())]) my_range = nodes.Range.create(lbound, ubound) datatype = symbols.INTEGER8_TYPE asref = nodes.ArrayOfStructuresReference.\ diff --git a/src/psyclone/tests/psyir/nodes/array_reference_test.py b/src/psyclone/tests/psyir/nodes/array_reference_test.py index 72d0c91fcc..405c45caac 100644 --- a/src/psyclone/tests/psyir/nodes/array_reference_test.py +++ b/src/psyclone/tests/psyir/nodes/array_reference_test.py @@ -43,7 +43,7 @@ from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.nodes.node import colored from psyclone.psyir.nodes import Reference, ArrayReference, Assignment, \ - Literal, BinaryOperation, Range, KernelSchedule + Literal, BinaryOperation, Range, KernelSchedule, IntrinsicCall from psyclone.psyir.symbols import ( ArrayType, DataSymbol, DataTypeSymbol, DeferredType, ScalarType, REAL_SINGLE_TYPE, INTEGER_SINGLE_TYPE, REAL_TYPE, INTEGER_TYPE) @@ -226,22 +226,23 @@ def test_array_is_lower_bound(): array2 = ArrayReference.create(DataSymbol("test2", ArrayType(REAL_TYPE, [10])), [one.copy()]) - operator = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, Reference(array2.symbol), - one.copy()) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(array2.symbol), ("dim", one.copy())]) array.children[0] = Range.create(operator, one.copy(), one.copy()) assert not array.is_lower_bound(0) # range node lbound references a different index - operator = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, Reference(array.symbol), - Literal("2", INTEGER_TYPE)) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(array.symbol), ("dim", Literal("2", INTEGER_TYPE))]) array.children[0] = Range.create(operator, one.copy(), one.copy()) assert not array.is_lower_bound(0) # all is well - operator = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, Reference(array.symbol), one.copy()) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(array.symbol), ("dim", one.copy())]) array.children[0] = Range.create(operator, one.copy(), one.copy()) assert array.is_lower_bound(0) @@ -271,21 +272,23 @@ def test_array_is_upper_bound(): array2 = ArrayReference.create(DataSymbol("test2", ArrayType(REAL_TYPE, [10])), [one.copy()]) - operator = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, Reference(array2.symbol), one.copy()) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(array2.symbol), ("dim", one.copy())]) array.children[0] = Range.create(one.copy(), operator, one.copy()) assert not array.is_upper_bound(0) # range node ubound references a different index - operator = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, Reference(array.symbol), - Literal("2", INTEGER_TYPE)) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(array.symbol), ("dim", Literal("2", INTEGER_TYPE))]) array.children[0] = Range.create(one.copy(), operator, one.copy()) assert not array.is_upper_bound(0) # all is well - operator = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, Reference(array.symbol), one.copy()) + operator = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(array.symbol), ("dim", one.copy())]) array.children[0] = Range.create(one.copy(), operator, one.copy()) assert array.is_upper_bound(0) @@ -299,10 +302,10 @@ def test_array_is_full_range(): array_type = ArrayType(REAL_SINGLE_TYPE, [10]) symbol = DataSymbol("my_array", array_type) reference = Reference(symbol) - lbound = BinaryOperation.create(BinaryOperation.Operator.LBOUND, - reference, one) - ubound = BinaryOperation.create(BinaryOperation.Operator.UBOUND, - reference.copy(), one.copy()) + lbound = IntrinsicCall.create(IntrinsicCall.Intrinsic.LBOUND, + [reference, ("dim", one)]) + ubound = IntrinsicCall.create(IntrinsicCall.Intrinsic.UBOUND, + [reference.copy(), ("dim", one.copy())]) symbol_error = DataSymbol("another_array", array_type) reference_error = Reference(symbol_error) @@ -329,95 +332,101 @@ def test_array_is_full_range(): # Array dimension range lower bound is an LBOUND binary operation # with the first value not being a reference - lbound_error = BinaryOperation.create(BinaryOperation.Operator.LBOUND, - zero.copy(), zero.copy()) + lbound_error = IntrinsicCall.create(IntrinsicCall.Intrinsic.LBOUND, + [zero.copy(), ("dim", zero.copy())]) my_range = Range.create(lbound_error, one.copy(), one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range lower bound is an LBOUND binary operation # with the first value being a reference to a different symbol - lbound_error = BinaryOperation.create(BinaryOperation.Operator.LBOUND, - reference_error, zero.copy()) + lbound_error = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [reference_error, ("dim", zero.copy())]) my_range = Range.create(lbound_error, one.copy(), one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range lower bound is an LBOUND binary operation # with the second value not being a literal. - lbound_error = BinaryOperation.create(BinaryOperation.Operator.LBOUND, - reference.copy(), reference.copy()) + lbound_error = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [reference.copy(), ("dim", reference.copy())]) my_range = Range.create(lbound_error, one.copy(), one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range lower bound is an LBOUND binary operation # with the second value not being an integer literal. - lbound_error = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, reference.copy(), - Literal("1.0", REAL_SINGLE_TYPE)) + lbound_error = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [reference.copy(), ("dim", Literal("1.0", REAL_SINGLE_TYPE))]) my_range = Range.create(lbound_error, one.copy(), one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) - # Array dimension range lower bound is an LBOUND binary operation + # Array dimension range lower bound is an LBOUND intrinsic # with the second value being an integer literal with the wrong # value (should be 0 as this dimension index is 0). - lbound_error = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, reference.copy(), one.copy()) + lbound_error = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [reference.copy(), ("dim", one.copy())]) my_range = Range.create(lbound_error, one.copy(), one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Check UBOUND - # Array dimension range upper bound is not a binary operation + # Array dimension range upper bound is not a intrinsic my_range = Range.create(lbound, one.copy(), one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) - # Array dimension range upper bound is not a UBOUND binary operation + # Array dimension range upper bound is not a UBOUND intrinsic my_range = Range.create(lbound.copy(), lbound.copy(), one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) - # Array dimension range upper bound is a UBOUND binary operation + # Array dimension range upper bound is a UBOUND intrinsic # with the first value not being a reference - ubound_error = BinaryOperation.create(BinaryOperation.Operator.UBOUND, - zero.copy(), zero.copy()) + ubound_error = IntrinsicCall.create(IntrinsicCall.Intrinsic.UBOUND, + [zero.copy(), ("dim", zero.copy())]) my_range = Range.create(lbound.copy(), ubound_error, one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) - # Array dimension range upper bound is a UBOUND binary operation + # Array dimension range upper bound is a UBOUND intrinsic # with the first value being a reference to a different symbol - ubound_error = BinaryOperation.create(BinaryOperation.Operator.UBOUND, - reference_error.copy(), zero.copy()) + ubound_error = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [reference_error.copy(), ("dim", zero.copy())]) my_range = Range.create(lbound.copy(), ubound_error, one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) - # Array dimension range upper bound is a UBOUND binary operation + # Array dimension range upper bound is a UBOUND intrinsic # with the second value not being a literal. - ubound_error = BinaryOperation.create(BinaryOperation.Operator.UBOUND, - reference.copy(), reference.copy()) + ubound_error = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [reference.copy(), ("dim", reference.copy())]) my_range = Range.create(lbound.copy(), ubound_error, one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) - # Array dimension range upper bound is a UBOUND binary operation + # Array dimension range upper bound is a UBOUND intrinsic # with the second value not being an integer literal. - ubound_error = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, reference.copy(), - Literal("1.0", REAL_SINGLE_TYPE)) + ubound_error = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [reference.copy(), ("dim", Literal("1.0", REAL_SINGLE_TYPE))]) my_range = Range.create(lbound.copy(), ubound_error, one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) - # Array dimension range upper bound is a UBOUND binary operation + # Array dimension range upper bound is a UBOUND intrinsic # with the second value being an integer literal with the wrong # value (should be 1 as this dimension is 1). - ubound_error = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, reference.copy(), zero.copy()) + ubound_error = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [reference.copy(), ("dim", zero.copy())]) my_range = Range.create(lbound.copy(), ubound_error, one.copy()) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) @@ -529,11 +538,11 @@ def test_array_create_colon(fortran_writer): # Check that each dimension is `lbound(...):ubound(...)` for child in aref.indices: assert isinstance(child, Range) - assert isinstance(child.children[1], BinaryOperation) - assert child.children[0].operator == \ - BinaryOperation.Operator.LBOUND - assert child.children[1].operator == \ - BinaryOperation.Operator.UBOUND + assert isinstance(child.children[1], IntrinsicCall) + assert child.children[0].intrinsic == \ + IntrinsicCall.Intrinsic.LBOUND + assert child.children[1].intrinsic == \ + IntrinsicCall.Intrinsic.UBOUND code = fortran_writer(aref) assert code == "test(:,:)" diff --git a/src/psyclone/tests/psyir/nodes/assignment_test.py b/src/psyclone/tests/psyir/nodes/assignment_test.py index 667ded063c..1a62cd61bd 100644 --- a/src/psyclone/tests/psyir/nodes/assignment_test.py +++ b/src/psyclone/tests/psyir/nodes/assignment_test.py @@ -42,13 +42,13 @@ from psyclone.errors import InternalError, GenerationError from psyclone.f2pygen import ModuleGen from psyclone.psyir.backend.fortran import FortranWriter -from psyclone.psyir.nodes import Assignment, Reference, Literal, \ - ArrayReference, Range, BinaryOperation, StructureReference, \ - ArrayOfStructuresReference, UnaryOperation, IntrinsicCall +from psyclone.psyir.nodes import ( + Assignment, Reference, Literal, ArrayReference, Range, StructureReference, + ArrayOfStructuresReference, IntrinsicCall) from psyclone.psyir.nodes.node import colored -from psyclone.psyir.symbols import DataSymbol, REAL_SINGLE_TYPE, Symbol, \ - INTEGER_SINGLE_TYPE, REAL_TYPE, ArrayType, INTEGER_TYPE, StructureType, \ - DataTypeSymbol +from psyclone.psyir.symbols import ( + DataSymbol, REAL_SINGLE_TYPE, Symbol, INTEGER_SINGLE_TYPE, REAL_TYPE, + ArrayType, INTEGER_TYPE, StructureType, DataTypeSymbol) from psyclone.tests.utilities import check_links @@ -165,12 +165,14 @@ def test_is_array_assignment(): field_symbol = DataSymbol("wind", field_type_symbol) # Array reference to component of derived type using a range - lbound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - StructureReference.create(field_symbol, ["data"]), int_one.copy()) - ubound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - StructureReference.create(field_symbol, ["data"]), int_one.copy()) + lbound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [StructureReference.create(field_symbol, ["data"]), + ("dim", int_one.copy())]) + ubound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [StructureReference.create(field_symbol, ["data"]), + ("dim", int_one.copy())]) my_range = Range.create(lbound, ubound) data_ref = StructureReference.create(field_symbol, [("data", [my_range])]) @@ -198,34 +200,29 @@ def test_is_array_assignment(): # e.g y(1, INT(ABS(map(:, 1)))) = 1.0 int_array_type = ArrayType(INTEGER_SINGLE_TYPE, [10, 10]) map_sym = DataSymbol("map", int_array_type) - lbound1 = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(map_sym), int_one.copy()) - ubound1 = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(map_sym), int_one.copy()) + lbound1 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(map_sym), ("dim", int_one.copy())]) + ubound1 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(map_sym), ("dim", int_one.copy())]) my_range1 = Range.create(lbound1, ubound1) - abs_op = UnaryOperation.create(UnaryOperation.Operator.ABS, - ArrayReference.create(map_sym, - [my_range1, - int_one.copy()])) - int_op = UnaryOperation.create(UnaryOperation.Operator.INT, abs_op) + abs_op = IntrinsicCall.create( + IntrinsicCall.Intrinsic.ABS, + [ArrayReference.create(map_sym, [my_range1, int_one.copy()])]) + int_op = IntrinsicCall.create(IntrinsicCall.Intrinsic.INT, [abs_op]) assignment = Assignment.create( ArrayReference.create(symbol, [int_one.copy(), int_op]), one.copy()) assert assignment.is_array_assignment is True -def test_array_assignment_with_reduction(monkeypatch): +def test_array_assignment_with_reduction(): '''Test that we correctly identify an array assignment when it is the result of a reduction from an array that returns an array. Test when we need to look up the PSyIR tree through multiple intrinsics - from the array access to find the reduction. We have to - monkeypatch SUM in this example to stop it being a reduction as - all IntrinsicCalls that are valid within an assignment are - currently reductions. When additional intrinsics are added (see - issue #1987) this test can be modified and monkeypatch - removed. The example is: x(1, MAXVAL(SUM(map(:, :), dim=1))) = 1.0 + from the array access to find the reduction. + The example is: x(1, MAXVAL(SIN(map(:, :)))) = 1.0 ''' one = Literal("1.0", REAL_TYPE) @@ -235,30 +232,27 @@ def test_array_assignment_with_reduction(monkeypatch): map_sym = DataSymbol("map", int_array_type) array_type = ArrayType(REAL_TYPE, [10, 10]) symbol = DataSymbol("x", array_type) - lbound1 = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(map_sym), int_one.copy()) - ubound1 = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(map_sym), int_one.copy()) + lbound1 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(map_sym), ("dim", int_one.copy())]) + ubound1 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(map_sym), ("dim", int_one.copy())]) my_range1 = Range.create(lbound1, ubound1) - lbound2 = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(map_sym), int_two.copy()) - ubound2 = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(map_sym), int_two.copy()) + lbound2 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(map_sym), ("dim", int_two.copy())]) + ubound2 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(map_sym), ("dim", int_two.copy())]) my_range2 = Range.create(lbound2, ubound2) bsum_op = IntrinsicCall.create( - IntrinsicCall.Intrinsic.SUM, - [ArrayReference.create(map_sym, [my_range1, my_range2]), - ("dim", int_one.copy())]) + IntrinsicCall.Intrinsic.SIN, + [ArrayReference.create(map_sym, [my_range1, my_range2])]) maxval_op = IntrinsicCall.create(IntrinsicCall.Intrinsic.MAXVAL, [bsum_op]) assignment = Assignment.create( ArrayReference.create(symbol, [int_one.copy(), maxval_op]), one.copy()) - monkeypatch.setattr( - bsum_op, "_intrinsic", IntrinsicCall.Intrinsic.ALLOCATE) if not assignment.is_array_assignment: # is_array_assignment should return True pytest.xfail(reason="#658 needs typing of PSyIR expressions") @@ -289,10 +283,10 @@ def test_is_not_array_assignment(): # using an array range, y(1, SUM(map(:), 1)) = 1.0 int_array_type = ArrayType(INTEGER_SINGLE_TYPE, [10]) map_sym = DataSymbol("map", int_array_type) - start = BinaryOperation.create(BinaryOperation.Operator.LBOUND, - Reference(map_sym), int_one.copy()) - stop = BinaryOperation.create(BinaryOperation.Operator.UBOUND, - Reference(map_sym), int_one.copy()) + start = IntrinsicCall.create(IntrinsicCall.Intrinsic.LBOUND, + [Reference(map_sym), ("dim", int_one.copy())]) + stop = IntrinsicCall.create(IntrinsicCall.Intrinsic.UBOUND, + [Reference(map_sym), ("dim", int_one.copy())]) my_range = Range.create(start, stop) sum_op = IntrinsicCall.create( IntrinsicCall.Intrinsic.SUM, @@ -304,9 +298,9 @@ def test_is_not_array_assignment(): # When the slice has two operator ancestors, one of which is a reduction # e.g y(1, SUM(ABS(map(:)), 1)) = 1.0 - abs_op = UnaryOperation.create(UnaryOperation.Operator.ABS, - ArrayReference.create(map_sym, - [my_range.copy()])) + abs_op = IntrinsicCall.create( + IntrinsicCall.Intrinsic.ABS, + [ArrayReference.create(map_sym, [my_range.copy()])]) sum_op2 = IntrinsicCall.create( IntrinsicCall.Intrinsic.SUM, [abs_op, ("dim", int_one.copy())]) assignment = Assignment.create( diff --git a/src/psyclone/tests/psyir/nodes/bound_intrinsic_op_test.py b/src/psyclone/tests/psyir/nodes/bound_intrinsic_op_test.py deleted file mode 100644 index 70f80129bd..0000000000 --- a/src/psyclone/tests/psyir/nodes/bound_intrinsic_op_test.py +++ /dev/null @@ -1,66 +0,0 @@ -# ----------------------------------------------------------------------------- -# BSD 3-Clause License -# -# Copyright (c) 2020-2021, Science and Technology Facilities Council. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# ----------------------------------------------------------------------------- -# Author: A. R. Porter, STFC Daresbury Lab -# ----------------------------------------------------------------------------- - -''' pytest module for the U/LBOUND intrinsic PSyIR operators. ''' - -from __future__ import absolute_import -import pytest -from psyclone.psyir.nodes import BinaryOperation, Literal, ArrayReference -from psyclone.psyir.symbols import (REAL_TYPE, INTEGER_TYPE, DataSymbol, - DeferredType) - - -@pytest.mark.xfail(reason="#677 the create() method does not check that the " - "types of the Nodes it is passed are correct for the " - "provided operator.") -@pytest.mark.parametrize("bound", [BinaryOperation.Operator.LBOUND, - BinaryOperation.Operator.UBOUND]) -def test_bound_intrinsic_wrong_type(bound): - ''' Check that attempting to create an L/UBOUND intrinsic operator - with the wrong type of arguments raises the expected error. ''' - int_one = Literal("1", INTEGER_TYPE) - with pytest.raises(TypeError) as err: - # First argument must be an Array - _ = BinaryOperation.create(bound, int_one.copy(), int_one.copy()) - assert "must be an Array but got: 'Literal" in str(err.value) - sym = DataSymbol("array", DeferredType()) - with pytest.raises(TypeError) as err: - # Second argument cannot be a real literal - _ = BinaryOperation.create( - bound, ArrayReference.create(sym, [int_one.copy()]), - Literal("1.0", REAL_TYPE)) - assert ("must be an integer but got a Literal of type REAL" in - str(err.value)) diff --git a/src/psyclone/tests/psyir/nodes/call_test.py b/src/psyclone/tests/psyir/nodes/call_test.py index 18bf29554a..d78a85113d 100644 --- a/src/psyclone/tests/psyir/nodes/call_test.py +++ b/src/psyclone/tests/psyir/nodes/call_test.py @@ -93,6 +93,14 @@ def test_call_is_pure(): assert call.is_pure is True +def test_call_is_available_on_device(): + '''Test the is_available_on_device() method of a Call (currently always + returns False). ''' + routine = RoutineSymbol("zaphod", NoType()) + call = Call(routine) + assert call.is_available_on_device() is False + + def test_call_equality(): '''Test the __eq__ method of the Call class. ''' # routine arguments diff --git a/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py b/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py index 2d8c378dae..960fb8bef8 100644 --- a/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py +++ b/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py @@ -32,15 +32,16 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: A. R. Porter, STFC Daresbury Lab -# Modified: R. W. Ford, STFC Daresbury Lab +# Modified: R. W. Ford and S. Siso, STFC Daresbury Lab # ----------------------------------------------------------------------------- '''This module contains pytest tests for the IntrinsicCall node.''' import pytest +from psyclone.core import VariablesAccessInfo from psyclone.psyir.nodes import ( - ArrayReference, Literal, IntrinsicCall, Reference, Schedule) + ArrayReference, Literal, IntrinsicCall, Reference, Schedule, Assignment) from psyclone.psyir.symbols import ( ArrayType, DataSymbol, INTEGER_TYPE, IntrinsicSymbol, REAL_TYPE, BOOLEAN_TYPE, CHARACTER_TYPE) @@ -93,6 +94,19 @@ def test_intrinsiccall_is_pure(): assert intrinsic.is_pure is False +@pytest.mark.parametrize("intrinsic, result", [ + (IntrinsicCall.Intrinsic.ABS, True), + (IntrinsicCall.Intrinsic.MIN, True), + (IntrinsicCall.Intrinsic.MAX, True), + (IntrinsicCall.Intrinsic.MAXVAL, False), + (IntrinsicCall.Intrinsic.ALLOCATE, False), + (IntrinsicCall.Intrinsic.MATMUL, False)]) +def test_intrinsiccall_is_available_on_device(intrinsic, result): + '''Tests that the is_available_on_device() method works as expected.''' + intrinsic_call = IntrinsicCall(intrinsic) + assert intrinsic_call.is_available_on_device() is result + + def test_intrinsiccall_alloc_create(): '''Tests the create() method supports various forms of 'allocate'. @@ -275,19 +289,25 @@ def test_intrinsiccall_create_errors(): [Reference(sym), ("stat", aref), aref]) assert ("Found a positional argument *after* a named argument ('stat'). " "This is invalid." in str(err.value)) - # 'random' does not have any optional arguments - with pytest.raises(ValueError) as err: - IntrinsicCall.create(IntrinsicCall.Intrinsic.RANDOM_NUMBER, - [aref, ("willow", sym)]) - assert ("The 'RANDOM_NUMBER' intrinsic does not support any optional " - "arguments but got 'willow'" in str(err.value)) + + # TODO #2303: We can not enable the validation of positional parameters + # unless we store their name, otherwise when we parse a positional argument + # by name, which is valid fortran, it will fail. + # (e.g. RANDOM_NUMBER(harvest=4) + + # with pytest.raises(ValueError) as err: + # IntrinsicCall.create(IntrinsicCall.Intrinsic.RANDOM_NUMBER, + # [aref, ("willow", sym)]) + # assert ("The 'RANDOM_NUMBER' intrinsic does not support any optional " + # "arguments but got 'willow'" in str(err.value)) # An allocate only supports the 'stat' and 'mold' arguments. - with pytest.raises(ValueError) as err: - IntrinsicCall.create(IntrinsicCall.Intrinsic.ALLOCATE, - [aref, ("yacht", Reference(sym))]) - assert ("The 'ALLOCATE' intrinsic supports the optional arguments " - "['errmsg', 'mold', 'source', 'stat'] but got 'yacht'" - in str(err.value)) + # with pytest.raises(ValueError) as err: + # IntrinsicCall.create(IntrinsicCall.Intrinsic.ALLOCATE, + # [aref, ("yacht", Reference(sym))]) + # assert ("The 'ALLOCATE' intrinsic supports the optional arguments " + # "['errmsg', 'mold', 'source', 'stat'] but got 'yacht'" + # in str(err.value)) + # Wrong type for the name of an optional argument. with pytest.raises(TypeError) as err: IntrinsicCall.create(IntrinsicCall.Intrinsic.ALLOCATE, @@ -301,3 +321,74 @@ def test_intrinsiccall_create_errors(): [aref, ("stat", sym)]) assert ("The optional argument 'stat' to intrinsic 'ALLOCATE' must be " "of type 'Reference' but got 'DataSymbol'" in str(err.value)) + + +def test_create_positional_arguments_with_names(): + ''' Test the create method when given named positional arguments.''' + sym = DataSymbol("my_array", + ArrayType(INTEGER_TYPE, [ArrayType.Extent.DEFERRED])) + aref = ArrayReference.create(sym, [Literal("20", INTEGER_TYPE)]) + bref = ArrayReference.create(sym, [Literal("20", INTEGER_TYPE)]) + + # All of these are valid + intr = IntrinsicCall.create(IntrinsicCall.Intrinsic.DOT_PRODUCT, + [aref.copy(), bref.copy()]) + assert isinstance(intr, IntrinsicCall) + assert intr.children[0] == aref + assert intr.children[1] == bref + assert intr.argument_names == [None, None] + + intr = IntrinsicCall.create(IntrinsicCall.Intrinsic.DOT_PRODUCT, + [aref.copy(), ("vector_b", bref.copy())]) + assert isinstance(intr, IntrinsicCall) + assert intr.children[0] == aref + assert intr.children[1] == bref + assert intr.argument_names == [None, "vector_b"] + + intr = IntrinsicCall.create(IntrinsicCall.Intrinsic.DOT_PRODUCT, + [("vector_a", aref.copy()), + ("vector_b", bref.copy())]) + assert isinstance(intr, IntrinsicCall) + assert intr.children[0] == aref + assert intr.children[1] == bref + assert intr.argument_names == ["vector_a", "vector_b"] + + intr = IntrinsicCall.create(IntrinsicCall.Intrinsic.DOT_PRODUCT, + [("vector_b", bref.copy()), + ("vector_a", aref.copy())]) + assert isinstance(intr, IntrinsicCall) + assert intr.children[0] == bref + assert intr.children[1] == aref + assert intr.argument_names == ["vector_b", "vector_a"] + + +@pytest.mark.parametrize("operator", ["lbound", "ubound", "size"]) +def test_reference_accesses_bounds(operator, fortran_reader): + '''Test that the reference_accesses method behaves as expected when + the reference is the first argument to either the lbound or ubound + intrinsic as that is simply looking up the array bounds (therefore + var_access_info should be empty) and when the reference is the + second argument of either the lbound or ubound intrinsic (in which + case the access should be a read). + + ''' + code = f'''module test + contains + subroutine tmp() + real, dimension(:,:), allocatable:: a, b + integer :: n + n = {operator}(a, b(1,1)) + end subroutine tmp + end module test''' + psyir = fortran_reader.psyir_from_source(code) + schedule = psyir.walk(Assignment)[0] + + # By default, the access to 'a' should not be reported as read, + # but the access to b must be reported: + vai = VariablesAccessInfo(schedule) + assert str(vai) == "b: READ, n: WRITE" + + # When explicitly requested, the access to 'a' should be reported: + vai = VariablesAccessInfo(schedule, + options={"COLLECT-ARRAY-SHAPE-READS": True}) + assert str(vai) == "a: READ, b: READ, n: WRITE" diff --git a/src/psyclone/tests/psyir/nodes/operation_test.py b/src/psyclone/tests/psyir/nodes/operation_test.py index c0a7a4f1b5..9ecbe9eaa9 100644 --- a/src/psyclone/tests/psyir/nodes/operation_test.py +++ b/src/psyclone/tests/psyir/nodes/operation_test.py @@ -42,309 +42,14 @@ ''' import pytest -from psyclone.core import VariablesAccessInfo from psyclone.psyir.nodes import UnaryOperation, BinaryOperation, \ - NaryOperation, Literal, Reference, Return + Literal, Reference, Return from psyclone.psyir.symbols import DataSymbol, INTEGER_SINGLE_TYPE, \ REAL_SINGLE_TYPE from psyclone.errors import GenerationError from psyclone.psyir.backend.fortran import FortranWriter from psyclone.tests.utilities import check_links -from psyclone.psyir.nodes import Assignment, colored - - -# Test Operation class. These are mostly covered by the subclass tests. - -def test_operation_named_arg_str(): - '''Check the output of str from the Operation class when there is a - mixture of positional and named arguments. We use a - BinaryOperation example to exercise this method. - - ''' - lhs = Reference(DataSymbol("tmp1", REAL_SINGLE_TYPE)) - rhs = Reference(DataSymbol("tmp2", REAL_SINGLE_TYPE)) - oper = BinaryOperation.Operator.DOT_PRODUCT - binaryoperation = BinaryOperation.create(oper, lhs, ("named_arg", rhs)) - assert "named_arg=Reference[name:'tmp2']" in str(binaryoperation) - - -def test_operation_appendnamedarg(): - '''Test the append_named_arg method in the Operation class. Check - it raises the expected exceptions if arguments are invalid and - that it works as expected when the input is valid. We use the - NaryOperation node to perform the tests. - - ''' - nary_operation = NaryOperation(NaryOperation.Operator.MAX) - op1 = Literal("1", INTEGER_SINGLE_TYPE) - op2 = Literal("2", INTEGER_SINGLE_TYPE) - op3 = Literal("3", INTEGER_SINGLE_TYPE) - # name arg wrong type - with pytest.raises(TypeError) as info: - nary_operation.append_named_arg(1, op1) - assert ("A name should be a string or None, but found int." - in str(info.value)) - # name arg invalid - with pytest.raises(ValueError) as info: - nary_operation.append_named_arg("_", op2) - assert "Invalid name '_' found." in str(info.value) - # name arg already used - nary_operation.append_named_arg("name1", op1) - with pytest.raises(ValueError) as info: - nary_operation.append_named_arg("name1", op2) - assert ("The value of the name argument (name1) in 'append_named_arg' in " - "the 'Operator' node is already used for a named argument." - in str(info.value)) - # ok - nary_operation.append_named_arg("name2", op2) - nary_operation.append_named_arg(None, op3) - assert nary_operation.children == [op1, op2, op3] - assert nary_operation.argument_names == ["name1", "name2", None] - # too many args - binary_operation = BinaryOperation.create( - BinaryOperation.Operator.DOT_PRODUCT, op1.copy(), op2.copy()) - with pytest.raises(GenerationError) as info: - binary_operation.append_named_arg(None, op3.copy()) - assert ("Item 'Literal' can't be child 2 of 'BinaryOperation'. The valid " - "format is: 'DataNode, DataNode'." in str(info.value)) - - -def test_operation_insertnamedarg(): - '''Test the insert_named_arg method in the Operation class. Check - it raises the expected exceptions if arguments are invalid and - that it works as expected when the input is valid. We use the - NaryOperation node to perform the tests. - - ''' - nary_operation = NaryOperation(NaryOperation.Operator.MAX) - op1 = Literal("1", INTEGER_SINGLE_TYPE) - op2 = Literal("2", INTEGER_SINGLE_TYPE) - op3 = Literal("3", INTEGER_SINGLE_TYPE) - # name arg wrong type - with pytest.raises(TypeError) as info: - nary_operation.insert_named_arg(1, op1, 0) - assert ("A name should be a string or None, but found int." - in str(info.value)) - # name arg invalid - with pytest.raises(ValueError) as info: - nary_operation.append_named_arg(" a", op2) - assert "Invalid name ' a' found." in str(info.value) - # name arg already used - nary_operation.insert_named_arg("name1", op1, 0) - with pytest.raises(ValueError) as info: - nary_operation.insert_named_arg("name1", op2, 0) - assert ("The value of the name argument (name1) in 'insert_named_arg' in " - "the 'Operator' node is already used for a named argument." - in str(info.value)) - # invalid index type - with pytest.raises(TypeError) as info: - nary_operation.insert_named_arg("name2", op2, "hello") - assert ("The 'index' argument in 'insert_named_arg' in the 'Operator' " - "node should be an int but found str." in str(info.value)) - # ok - assert nary_operation.children == [op1] - assert nary_operation.argument_names == ["name1"] - nary_operation.insert_named_arg("name2", op2, 0) - assert nary_operation.children == [op2, op1] - assert nary_operation.argument_names == ["name2", "name1"] - nary_operation.insert_named_arg(None, op3, 0) - assert nary_operation.children == [op3, op2, op1] - assert nary_operation.argument_names == [None, "name2", "name1"] - # invalid index value - binary_operation = BinaryOperation.create( - BinaryOperation.Operator.DOT_PRODUCT, op1.copy(), op2.copy()) - with pytest.raises(GenerationError) as info: - binary_operation.insert_named_arg("name2", op2.copy(), 2) - assert ("Item 'Literal' can't be child 2 of 'BinaryOperation'. The valid " - "format is: 'DataNode, DataNode'." in str(info.value)) - - -def test_operation_replacenamedarg(): - '''Test the replace_named_arg method in the Operation class. Check - it raises the expected exceptions if arguments are invalid and - that it works as expected when the input is valid. We use the - BinaryOperation node to perform the tests. - - ''' - binary_operation = BinaryOperation(BinaryOperation.Operator.DOT_PRODUCT) - op1 = Literal("1", INTEGER_SINGLE_TYPE) - op2 = Literal("2", INTEGER_SINGLE_TYPE) - op3 = Literal("3", INTEGER_SINGLE_TYPE) - binary_operation.append_named_arg("name1", op1) - binary_operation.append_named_arg("name2", op2) - - # name arg wrong type - with pytest.raises(TypeError) as info: - binary_operation.replace_named_arg(1, op3) - assert ("The 'name' argument in 'replace_named_arg' in the 'Operation' " - "node should be a string, but found int." - in str(info.value)) - # name arg is not found - with pytest.raises(ValueError) as info: - binary_operation.replace_named_arg("new_name", op3) - assert ("The value of the existing_name argument (new_name) in " - "'replace_named_arg' in the 'Operation' node was not found in the " - "existing arguments." in str(info.value)) - # ok - assert binary_operation.children == [op1, op2] - assert binary_operation.argument_names == ["name1", "name2"] - assert binary_operation._argument_names[0][0] == id(op1) - assert binary_operation._argument_names[1][0] == id(op2) - binary_operation.replace_named_arg("name1", op3) - assert binary_operation.children == [op3, op2] - assert binary_operation.argument_names == ["name1", "name2"] - assert binary_operation._argument_names[0][0] == id(op3) - assert binary_operation._argument_names[1][0] == id(op2) - - -def test_operation_argumentnames_after_removearg(): - '''Test the argument_names property makes things consistent if a child - argument is removed. This is used transparently by the class to - keep things consistent. We use the BinaryOperation node to perform - the tests. - - ''' - binary_operation = BinaryOperation(BinaryOperation.Operator.DOT_PRODUCT) - op1 = Literal("1", INTEGER_SINGLE_TYPE) - op2 = Literal("1", INTEGER_SINGLE_TYPE) - binary_operation.append_named_arg("name1", op1) - binary_operation.append_named_arg("name2", op2) - assert len(binary_operation.children) == 2 - assert len(binary_operation._argument_names) == 2 - assert binary_operation.argument_names == ["name1", "name2"] - binary_operation.children.pop(0) - assert len(binary_operation.children) == 1 - assert len(binary_operation._argument_names) == 2 - # argument_names property makes _argument_names list consistent. - assert binary_operation.argument_names == ["name2"] - assert len(binary_operation._argument_names) == 1 - - -def test_operation_argumentnames_after_addarg(): - '''Test the argument_names property makes things consistent if a child - argument is added. This is used transparently by the class to - keep things consistent. We use the NaryOperation node to perform - the tests (as it allows an arbitrary number of arguments. - - ''' - nary_operation = NaryOperation(NaryOperation.Operator.MAX) - op1 = Literal("1", INTEGER_SINGLE_TYPE) - op2 = Literal("1", INTEGER_SINGLE_TYPE) - op3 = Literal("1", INTEGER_SINGLE_TYPE) - nary_operation.append_named_arg("name1", op1) - nary_operation.append_named_arg("name2", op2) - assert len(nary_operation.children) == 2 - assert len(nary_operation._argument_names) == 2 - assert nary_operation.argument_names == ["name1", "name2"] - nary_operation.children.append(op3) - assert len(nary_operation.children) == 3 - assert len(nary_operation._argument_names) == 2 - # argument_names property makes _argument_names list consistent. - assert nary_operation.argument_names == ["name1", "name2", None] - assert len(nary_operation._argument_names) == 3 - - -def test_operation_argumentnames_after_replacearg(): - '''Test the argument_names property makes things consistent if a child - argument is replaced with another. This is used transparently by - the class to keep things consistent. We use the BinaryOperation - node to perform the tests. - - ''' - binary_operation = BinaryOperation(BinaryOperation.Operator.DOT_PRODUCT) - op1 = Literal("1", INTEGER_SINGLE_TYPE) - op2 = Literal("1", INTEGER_SINGLE_TYPE) - op3 = Literal("1", INTEGER_SINGLE_TYPE) - binary_operation.append_named_arg("name1", op1) - binary_operation.append_named_arg("name2", op2) - assert len(binary_operation.children) == 2 - assert len(binary_operation._argument_names) == 2 - assert binary_operation.argument_names == ["name1", "name2"] - binary_operation.children[0] = op3 - assert len(binary_operation.children) == 2 - assert len(binary_operation._argument_names) == 2 - # argument_names property makes _argument_names list consistent. - assert binary_operation.argument_names == [None, "name2"] - assert len(binary_operation._argument_names) == 2 - - -def test_operation_argumentnames_after_reorderearg(): - '''Test the argument_names property makes things consistent if child - arguments are re-order. This is used transparently by the class to - keep things consistent. We use the BinaryOperation node to perform - the tests. - - ''' - binary_operation = BinaryOperation(BinaryOperation.Operator.DOT_PRODUCT) - op1 = Literal("1", INTEGER_SINGLE_TYPE) - op2 = Literal("1", INTEGER_SINGLE_TYPE) - binary_operation.append_named_arg("name1", op1) - binary_operation.append_named_arg("name2", op2) - assert len(binary_operation.children) == 2 - assert len(binary_operation._argument_names) == 2 - assert binary_operation.argument_names == ["name1", "name2"] - tmp0 = binary_operation.children[0] - tmp1 = binary_operation.children[1] - tmp0.detach() - tmp1.detach() - binary_operation.children.extend([tmp1, tmp0]) - assert len(binary_operation.children) == 2 - assert len(binary_operation._argument_names) == 2 - # argument_names property makes _argument_names list consistent. - assert binary_operation.argument_names == ["name2", "name1"] - assert len(binary_operation._argument_names) == 2 - - -def test_operation_reconcile_add(): - '''Test that the reconcile method behaves as expected. Use an - NaryOperation example where we add a new arg. - - ''' - op1 = Literal("1", INTEGER_SINGLE_TYPE) - op2 = Literal("1", INTEGER_SINGLE_TYPE) - op3 = Literal("1", INTEGER_SINGLE_TYPE) - oper = NaryOperation.create( - NaryOperation.Operator.MAX, [("name1", op1), ("name2", op2)]) - # consistent - assert len(oper._argument_names) == 2 - assert oper._argument_names[0] == (id(oper.children[0]), "name1") - assert oper._argument_names[1] == (id(oper.children[1]), "name2") - oper.children.append(op3) - # inconsistent - assert len(oper._argument_names) == 2 - assert oper._argument_names[0] == (id(oper.children[0]), "name1") - assert oper._argument_names[1] == (id(oper.children[1]), "name2") - oper._reconcile() - # consistent - assert len(oper._argument_names) == 3 - assert oper._argument_names[0] == (id(oper.children[0]), "name1") - assert oper._argument_names[1] == (id(oper.children[1]), "name2") - assert oper._argument_names[2] == (id(oper.children[2]), None) - - -def test_operation_reconcile_reorder(): - '''Test that the reconcile method behaves as expected. Use a - BinaryOperation example where we reorder the arguments. - - ''' - op1 = Literal("1", INTEGER_SINGLE_TYPE) - op2 = Literal("2", INTEGER_SINGLE_TYPE) - oper = BinaryOperation.create( - BinaryOperation.Operator.DOT_PRODUCT, ("name1", op1), ("name2", op2)) - # consistent - assert len(oper._argument_names) == 2 - assert oper._argument_names[0] == (id(oper.children[0]), "name1") - assert oper._argument_names[1] == (id(oper.children[1]), "name2") - oper.children = [op2.detach(), op1.detach()] - # inconsistent - assert len(oper._argument_names) == 2 - assert oper._argument_names[0] != (id(oper.children[0]), "name1") - assert oper._argument_names[1] != (id(oper.children[1]), "name2") - oper._reconcile() - # consistent - assert len(oper._argument_names) == 2 - assert oper._argument_names[0] == (id(oper.children[0]), "name2") - assert oper._argument_names[1] == (id(oper.children[1]), "name1") +from psyclone.psyir.nodes import colored # Test BinaryOperation class @@ -410,26 +115,6 @@ def test_binaryoperation_create(): assert result == "tmp1 + tmp2" -def test_binaryoperation_named_create(): - '''Test that the create method in the BinaryOperation class correctly - creates a BinaryOperation instance when one or more of the - arguments is a named argument. - - ''' - lhs = Reference(DataSymbol("tmp1", REAL_SINGLE_TYPE)) - rhs = Reference(DataSymbol("tmp2", REAL_SINGLE_TYPE)) - oper = BinaryOperation.Operator.DOT_PRODUCT - binaryoperation = BinaryOperation.create(oper, lhs, ("dim", rhs)) - check_links(binaryoperation, [lhs, rhs]) - result = FortranWriter().binaryoperation_node(binaryoperation) - assert result == "DOT_PRODUCT(tmp1, dim=tmp2)" - binaryoperation = BinaryOperation.create( - oper, ("dummy", lhs.detach()), ("dim", rhs.detach())) - check_links(binaryoperation, [lhs, rhs]) - result = FortranWriter().binaryoperation_node(binaryoperation) - assert result == "DOT_PRODUCT(dummy=tmp1, dim=tmp2)" - - def test_binaryoperation_create_invalid(): '''Test that the create method in a BinaryOperation class raises the expected exception if the provided input is invalid. @@ -458,28 +143,6 @@ def test_binaryoperation_create_invalid(): assert ("Item 'str' can't be child 1 of 'BinaryOperation'. The valid " "format is: 'DataNode, DataNode'.") in str(excinfo.value) - # rhs is an invalid tuple (too many elements) - oper = BinaryOperation.Operator.DOT_PRODUCT - with pytest.raises(GenerationError) as excinfo: - _ = BinaryOperation.create(oper, ref1, (1, 2, 3)) - assert ("If the rhs argument in create method of BinaryOperation class " - "is a tuple, it's length should be 2, but it is 3." - in str(excinfo.value)) - - # rhs is an invalid tuple (1st element not str) - oper = BinaryOperation.Operator.DOT_PRODUCT - with pytest.raises(GenerationError) as excinfo: - _ = BinaryOperation.create(oper, ref1, (1, 2)) - assert ("If the rhs argument in create method of BinaryOperation class " - "is a tuple, its first argument should be a str, but found " - "int." in str(excinfo.value)) - - # rhs has an invalid name (1st element invalid value) - oper = BinaryOperation.Operator.DOT_PRODUCT - with pytest.raises(ValueError) as info: - _ = BinaryOperation.create(oper, ref1.copy(), ("_", 2)) - assert "Invalid name '_' found." in str(info.value) - def test_binaryoperation_children_validation(): '''Test that children added to BinaryOperation are validated. @@ -507,29 +170,6 @@ def test_binaryoperation_children_validation(): "format is: 'DataNode, DataNode'.") in str(excinfo.value) -def test_binaryoperation_is_elemental(): - '''Test that the is_elemental method properly returns if an operation is - elemental in each BinaryOperation. - - ''' - # MATMUL, SIZE, LBOUND, UBOUND and DOT_PRODUCT are not - # elemental - not_elemental = [ - BinaryOperation.Operator.SIZE, - BinaryOperation.Operator.MATMUL, - BinaryOperation.Operator.LBOUND, - BinaryOperation.Operator.UBOUND, - BinaryOperation.Operator.DOT_PRODUCT - ] - - for binary_operator in BinaryOperation.Operator: - operation = BinaryOperation(binary_operator) - if binary_operator in not_elemental: - assert operation.is_elemental is False - else: - assert operation.is_elemental is True - - # Test UnaryOperation class def test_unaryoperation_initialization(): ''' Check the initialization method of the UnaryOperation class works @@ -543,11 +183,7 @@ def test_unaryoperation_initialization(): assert uop._operator is UnaryOperation.Operator.MINUS -@pytest.mark.parametrize("operator_name", ['MINUS', 'MINUS', 'PLUS', 'SQRT', - 'EXP', 'LOG', 'LOG10', 'NOT', - 'COS', 'SIN', 'TAN', 'ACOS', - 'ASIN', 'ATAN', 'ABS', 'CEIL', - 'FLOOR', 'REAL', 'INT', 'NINT']) +@pytest.mark.parametrize("operator_name", ['MINUS', 'PLUS', 'NOT']) def test_unaryoperation_operator(operator_name): '''Test that the operator property returns the unaryoperator in the unaryoperation. @@ -585,68 +221,11 @@ def test_unaryoperation_create(): ''' child = Reference(DataSymbol("tmp", REAL_SINGLE_TYPE)) - oper = UnaryOperation.Operator.SIN + oper = UnaryOperation.Operator.MINUS unaryoperation = UnaryOperation.create(oper, child) check_links(unaryoperation, [child]) result = FortranWriter().unaryoperation_node(unaryoperation) - assert result == "SIN(tmp)" - - -def test_unaryoperation_named_create(): - '''Test that the create method in the UnaryOperation class correctly - creates a UnaryOperation instance when there is a named argument. - - ''' - child = Reference(DataSymbol("tmp", REAL_SINGLE_TYPE)) - oper = UnaryOperation.Operator.SIN - unaryoperation = UnaryOperation.create(oper, ("name", child)) - assert unaryoperation.argument_names == ["name"] - check_links(unaryoperation, [child]) - result = FortranWriter().unaryoperation_node(unaryoperation) - assert result == "SIN(name=tmp)" - - -def test_unaryoperation_create_invalid1(): - '''Test that the create method in a UnaryOperation class raises the - expected exception if the provided argument is a tuple that does - not have 2 elements. - - ''' - # oper not a UnaryOperator.Operator. - oper = UnaryOperation.Operator.SIN - with pytest.raises(GenerationError) as excinfo: - _ = UnaryOperation.create( - oper, (1, 2, 3)) - assert ("If the argument in the create method of UnaryOperation class is " - "a tuple, it's length should be 2, but it is 3." - in str(excinfo.value)) - - -def test_unaryoperation_create_invalid2(): - '''Test that the create method in a UnaryOperation class raises the - expected exception if the provided argument is a tuple and the - first element of the tuple is not a string. - - ''' - # oper not a UnaryOperator.Operator. - oper = UnaryOperation.Operator.SIN - with pytest.raises(GenerationError) as excinfo: - _ = UnaryOperation.create( - oper, (1, 2)) - assert ("If the argument in the create method of UnaryOperation class " - "is a tuple, its first argument should be a str, but found int." - in str(excinfo.value)) - - -def test_unaryoperation_create_invalid3(): - '''Test that the create method in a UnaryOperation class raises the - expected exception a named argument is provided with an invalid name. - - ''' - oper = UnaryOperation.Operator.SIN - with pytest.raises(ValueError) as info: - _ = UnaryOperation.create(oper, ("1", 2)) - assert "Invalid name '1' found." in str(info.value) + assert result == "-tmp" def test_unaryoperation_create_invalid4(): @@ -670,7 +249,7 @@ def test_unaryoperation_children_validation(): UnaryOperations accept just 1 DataNode as child. ''' - operation = UnaryOperation(UnaryOperation.Operator.SIN) + operation = UnaryOperation(UnaryOperation.Operator.MINUS) literal1 = Literal("1", INTEGER_SINGLE_TYPE) literal2 = Literal("2", INTEGER_SINGLE_TYPE) statement = Return() @@ -689,210 +268,33 @@ def test_unaryoperation_children_validation(): "format is: 'DataNode'.") in str(excinfo.value) -def test_unaryoperation_is_elemental(): - '''Test that the is_elemental method properly returns if an operation is - elemental in each UnaryOperation. - - ''' - # All unary operators are elemental - for unary_operator in UnaryOperation.Operator: - operation = UnaryOperation(unary_operator) - assert operation.is_elemental is True - - -# Test NaryOperation class -def test_naryoperation_node_str(): - ''' Check the node_str method of the Nary Operation class.''' - nary_operation = NaryOperation(NaryOperation.Operator.MAX) - nary_operation.addchild(Literal("1", INTEGER_SINGLE_TYPE)) - nary_operation.addchild(Literal("1", INTEGER_SINGLE_TYPE)) - nary_operation.addchild(Literal("1", INTEGER_SINGLE_TYPE)) - - coloredtext = colored("NaryOperation", NaryOperation._colour) - assert coloredtext+"[operator:'MAX']" in nary_operation.node_str() - - -def test_naryoperation_can_be_printed(): - '''Test that an Nary Operation instance can always be printed (i.e. is - initialised fully)''' - nary_operation = NaryOperation(NaryOperation.Operator.MAX) - assert "NaryOperation[operator:'MAX']" in str(nary_operation) - nary_operation.addchild(Literal("1", INTEGER_SINGLE_TYPE)) - nary_operation.addchild(Literal("2", INTEGER_SINGLE_TYPE)) - nary_operation.addchild(Literal("3", INTEGER_SINGLE_TYPE)) - # Check the node children are also printed - assert ("Literal[value:'1', Scalar]\n" - in str(nary_operation)) - assert ("Literal[value:'2', Scalar]\n" - in str(nary_operation)) - assert ("Literal[value:'3', Scalar]" - in str(nary_operation)) - - -def test_naryoperation_create(): - '''Test that the create method in the NaryOperation class correctly - creates an NaryOperation instance. - - ''' - children = [Reference(DataSymbol("tmp1", REAL_SINGLE_TYPE)), - Reference(DataSymbol("tmp2", REAL_SINGLE_TYPE)), - Reference(DataSymbol("tmp3", REAL_SINGLE_TYPE))] - oper = NaryOperation.Operator.MAX - naryoperation = NaryOperation.create(oper, children) - check_links(naryoperation, children) - result = FortranWriter().naryoperation_node(naryoperation) - assert result == "MAX(tmp1, tmp2, tmp3)" - - -def test_naryoperation_named_create(): - '''Test that the create method in the NaryOperation class correctly - creates a NaryOperation instance when one of the arguments is a - named argument. - - ''' - children = [Reference(DataSymbol("tmp1", REAL_SINGLE_TYPE)), - Reference(DataSymbol("tmp2", REAL_SINGLE_TYPE)), - Reference(DataSymbol("tmp3", REAL_SINGLE_TYPE))] - oper = NaryOperation.Operator.MAX - naryoperation = NaryOperation.create( - oper, [children[0], children[1], ("name", children[2])]) - check_links(naryoperation, children) - result = FortranWriter().naryoperation_node(naryoperation) - assert result == "MAX(tmp1, tmp2, name=tmp3)" - - -def test_naryoperation_create_invalid(): - '''Test that the create method in an NaryOperation class raises the - expected exception if the provided input is invalid. - - ''' - # oper not an NaryOperation.Operator - with pytest.raises(GenerationError) as excinfo: - _ = NaryOperation.create("invalid", []) - assert ("operator argument in create method of NaryOperation class should " - "be a PSyIR NaryOperation Operator but found 'str'." - in str(excinfo.value)) - - oper = NaryOperation.Operator.MAX - - # children not a list - with pytest.raises(GenerationError) as excinfo: - _ = NaryOperation.create(oper, "invalid") - assert ("operands argument in create method of NaryOperation class should " - "be a list but found 'str'." in str(excinfo.value)) - - ref1 = Reference(DataSymbol("tmp1", REAL_SINGLE_TYPE)) - ref2 = Reference(DataSymbol("tmp2", REAL_SINGLE_TYPE)) - - # rhs is an invalid tuple (too many elements) - with pytest.raises(GenerationError) as excinfo: - _ = NaryOperation.create(oper, [ref1, ref2, (1, 2, 3)]) - assert ("If an element of the operands argument in create method of " - "NaryOperation class is a tuple, it's length should be 2, " - "but found 3." in str(excinfo.value)) - - # rhs is an invalid tuple (1st element not str) - with pytest.raises(GenerationError) as excinfo: - _ = NaryOperation.create(oper, [ref1.copy(), ref2.copy(), (1, 2)]) - assert ("If an element of the operands argument in create method of " - "NaryOperation class is a tuple, its first argument should " - "be a str, but found int." in str(excinfo.value)) - - -def test_naryoperation_children_validation(): - '''Test that children added to NaryOperation are validated. NaryOperations - accepts DataNodes nodes as children. - - ''' - nary = NaryOperation(NaryOperation.Operator.MAX) - literal1 = Literal("1", INTEGER_SINGLE_TYPE) - literal2 = Literal("2", INTEGER_SINGLE_TYPE) - literal3 = Literal("3", INTEGER_SINGLE_TYPE) - statement = Return() - - # DataNodes are valid - nary.addchild(literal1) - nary.addchild(literal2) - nary.addchild(literal3) - - # Statements are not valid - with pytest.raises(GenerationError) as excinfo: - nary.addchild(statement) - assert ("Item 'Return' can't be child 3 of 'NaryOperation'. The valid " - "format is: '[DataNode]+'.") in str(excinfo.value) - - -def test_naryoperation_is_elemental(): - '''Test that the is_elemental method properly returns if an operation is - elemental in each NaryOperation. - - ''' - # All nary operations are elemental - for nary_operator in NaryOperation.Operator: - operation = NaryOperation(nary_operator) - assert operation.is_elemental is True - - def test_operations_can_be_copied(): ''' Test that an operation can be copied. ''' operands = [Reference(DataSymbol("tmp1", REAL_SINGLE_TYPE)), - Reference(DataSymbol("tmp2", REAL_SINGLE_TYPE)), - Reference(DataSymbol("tmp3", REAL_SINGLE_TYPE))] - operation = NaryOperation.create(NaryOperation.Operator.MAX, operands) + Reference(DataSymbol("tmp2", REAL_SINGLE_TYPE))] + operation = BinaryOperation.create(BinaryOperation.Operator.ADD, *operands) operation1 = operation.copy() - assert isinstance(operation1, NaryOperation) + assert isinstance(operation1, BinaryOperation) assert operation1 is not operation - assert operation1.operator is NaryOperation.Operator.MAX + assert operation1.operator is BinaryOperation.Operator.ADD assert operation1.children[0].symbol.name == "tmp1" assert operation1.children[0] is not operands[0] assert operation1.children[0].parent is operation1 assert operation1.children[1].symbol.name == "tmp2" assert operation1.children[1] is not operands[1] assert operation1.children[1].parent is operation1 - assert operation1.children[2].symbol.name == "tmp3" - assert operation1.children[2] is not operands[2] - assert operation1.children[2].parent is operation1 - assert len(operation1.children) == 3 - assert len(operation.children) == 3 + assert len(operation1.children) == 2 + assert len(operation.children) == 2 # Modifying the new operation does not affect the original - operation1._operator = NaryOperation.Operator.MIN + operation1._operator = BinaryOperation.Operator.MUL operation1.children.pop() - assert len(operation1.children) == 2 - assert len(operation.children) == 3 - assert operation1.operator is NaryOperation.Operator.MIN - assert operation.operator is NaryOperation.Operator.MAX - - -def test_copy(): - '''Test that the copy() method behaves as expected when there are - named arguments. - - ''' - op1 = Literal("1", INTEGER_SINGLE_TYPE) - op2 = Literal("2", INTEGER_SINGLE_TYPE) - oper = BinaryOperation.create( - BinaryOperation.Operator.DOT_PRODUCT, ("name1", op1), ("name2", op2)) - # consistent operation - oper_copy = oper.copy() - assert oper._argument_names[0] == (id(oper.children[0]), "name1") - assert oper._argument_names[1] == (id(oper.children[1]), "name2") - assert oper_copy._argument_names[0] == (id(oper_copy.children[0]), "name1") - assert oper_copy._argument_names[1] == (id(oper_copy.children[1]), "name2") - assert oper._argument_names != oper_copy._argument_names - - oper.children = [op2.detach(), op1.detach()] - assert oper._argument_names[0] != (id(oper.children[0]), "name2") - assert oper._argument_names[1] != (id(oper.children[1]), "name1") - # inconsistent operation - oper_copy = oper.copy() - assert oper._argument_names[0] == (id(oper.children[0]), "name2") - assert oper._argument_names[1] == (id(oper.children[1]), "name1") - assert oper_copy._argument_names[0] == (id(oper_copy.children[0]), "name2") - assert oper_copy._argument_names[1] == (id(oper_copy.children[1]), "name1") - assert oper._argument_names != oper_copy._argument_names + assert len(operation1.children) == 1 + assert len(operation.children) == 2 + assert operation1.operator is BinaryOperation.Operator.MUL + assert operation.operator is BinaryOperation.Operator.ADD def test_operation_equality(): @@ -912,50 +314,3 @@ def test_operation_equality(): # change the operator binaryoperation2._operator = BinaryOperation.Operator.SUB assert binaryoperation1 != binaryoperation2 - - # Check with arguments names - binaryoperation3 = BinaryOperation.create( - oper, ("name1", lhs.copy()), rhs.copy()) - binaryoperation4 = BinaryOperation.create( - oper, ("name1", lhs.copy()), rhs.copy()) - assert binaryoperation3 == binaryoperation4 - - # Check with argument name and no argument name - assert binaryoperation3 != binaryoperation1 - - # Check with different argument names - binaryoperation5 = BinaryOperation.create( - oper, ("new_name", lhs.copy()), rhs.copy()) - assert binaryoperation3 != binaryoperation5 - - -@pytest.mark.parametrize("operator", ["lbound", "ubound", "size"]) -def test_reference_accesses_bounds(operator, fortran_reader): - '''Test that the reference_accesses method behaves as expected when - the reference is the first argument to either the lbound or ubound - intrinsic as that is simply looking up the array bounds (therefore - var_access_info should be empty) and when the reference is the - second argument of either the lbound or ubound intrinsic (in which - case the access should be a read). - - ''' - code = f'''module test - contains - subroutine tmp() - real, dimension(:,:), allocatable:: a, b - integer :: n - n = {operator}(a, b(1,1)) - end subroutine tmp - end module test''' - psyir = fortran_reader.psyir_from_source(code) - schedule = psyir.walk(Assignment)[0] - - # By default, the access to 'a' should not be reported as read, - # but the access to b must be reported: - vai = VariablesAccessInfo(schedule) - assert str(vai) == "b: READ, n: WRITE" - - # When explicitly requested, the access to 'a' should be reported: - vai = VariablesAccessInfo(schedule, - options={"COLLECT-ARRAY-SHAPE-READS": True}) - assert str(vai) == "a: READ, b: READ, n: WRITE" diff --git a/src/psyclone/tests/psyir/nodes/type_convert_operation_test.py b/src/psyclone/tests/psyir/nodes/type_convert_intrinsic_test.py similarity index 65% rename from src/psyclone/tests/psyir/nodes/type_convert_operation_test.py rename to src/psyclone/tests/psyir/nodes/type_convert_intrinsic_test.py index 64877b903e..3f8eccbd4c 100644 --- a/src/psyclone/tests/psyir/nodes/type_convert_operation_test.py +++ b/src/psyclone/tests/psyir/nodes/type_convert_intrinsic_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2021, Science and Technology Facilities Council. +# Copyright (c) 2021-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,26 +32,26 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: A. R. Porter, STFC Daresbury Lab +# Modified: S. Siso, STFC Daresbury Lab # ----------------------------------------------------------------------------- ''' Performs pytest tests specific to the REAL/INT type-conversion - BinaryOperations. ''' + intrinsics. ''' -from __future__ import absolute_import import pytest -from psyclone.psyir.nodes import BinaryOperation, Literal, Reference +from psyclone.psyir.nodes import BinaryOperation, Literal, Reference, \ + IntrinsicCall from psyclone.psyir.symbols import DataSymbol, INTEGER_SINGLE_TYPE, \ REAL_SINGLE_TYPE -from psyclone.psyir.backend.fortran import FortranWriter from psyclone.tests.utilities import check_links -@pytest.mark.parametrize("operation, op_str", - [(BinaryOperation.Operator.REAL, "real"), - (BinaryOperation.Operator.INT, "int")]) -def test_type_convert_binaryop_create(operation, op_str): - '''Test that the create method in the BinaryOperation class correctly - creates a BinaryOperation instance for the REAL and INT type-conversion +@pytest.mark.parametrize("intrinsic, intr_str", + [(IntrinsicCall.Intrinsic.REAL, "real"), + (IntrinsicCall.Intrinsic.INT, "int")]) +def test_type_convert_intrinsic_create(intrinsic, intr_str, fortran_writer): + '''Test that the create method in the IntrinsicCall class correctly + creates a IntrinsicCall instance for the REAL and INT type-conversion operations.. ''' @@ -60,44 +60,47 @@ def test_type_convert_binaryop_create(operation, op_str): wp_sym = DataSymbol("wp", INTEGER_SINGLE_TYPE) # Reference to a kind parameter rhs = Reference(wp_sym) - binaryoperation = BinaryOperation.create(operation, lhs, rhs) - assert binaryoperation._operator is operation - check_links(binaryoperation, [lhs, rhs]) - result = FortranWriter().binaryoperation_node(binaryoperation) - assert op_str + "(tmp1, wp)" in result.lower() + intr_call = IntrinsicCall.create(intrinsic, [lhs, ("kind", rhs)]) + assert intr_call.intrinsic is intrinsic + check_links(intr_call, [lhs, rhs]) + result = fortran_writer(intr_call) + assert intr_str + "(tmp1, kind=wp)" in result.lower() # Kind specified with an integer literal rhs = Literal("4", INTEGER_SINGLE_TYPE) - binaryoperation = BinaryOperation.create(operation, lhs.detach(), rhs) - check_links(binaryoperation, [lhs, rhs]) - result = FortranWriter().binaryoperation_node(binaryoperation) - assert op_str + "(tmp1, 4)" in result.lower() + intr_call = IntrinsicCall.create(intrinsic, [lhs.detach(), ("kind", rhs)]) + check_links(intr_call, [lhs, rhs]) + result = fortran_writer(intr_call) + assert intr_str + "(tmp1, kind=4)" in result.lower() # Kind specified as an arithmetic expression rhs = BinaryOperation.create(BinaryOperation.Operator.ADD, Reference(wp_sym), Literal("2", INTEGER_SINGLE_TYPE)) - binaryoperation = BinaryOperation.create(operation, lhs.detach(), rhs) - check_links(binaryoperation, [lhs, rhs]) - result = FortranWriter().binaryoperation_node(binaryoperation) - assert op_str + "(tmp1, wp + 2)" in result.lower() + intr_call = IntrinsicCall.create(intrinsic, [lhs.detach(), ("kind", rhs)]) + check_links(intr_call, [lhs, rhs]) + result = fortran_writer(intr_call) + assert intr_str + "(tmp1, kind=wp + 2)" in result.lower() -@pytest.mark.xfail(reason="Only limited checking is performed on the " - "arguments supplied to the BinaryOperation.create() " +@pytest.mark.xfail(reason="No PSyIR symbol type checking is performed on the " + "arguments supplied to the IntrinsicCall.create() " "method - TODO #658.") -def test_real_binaryop_invalid(): +def test_real_intrinsic_invalid(): ''' Test that the create method rejects invalid precisions. ''' sym = DataSymbol("tmp1", REAL_SINGLE_TYPE) - oper = BinaryOperation.Operator.REAL + intrinsic = IntrinsicCall.Intrinsic.REAL with pytest.raises(TypeError) as err: - _ = BinaryOperation.create(oper, Reference(sym), - Literal("1.0", REAL_SINGLE_TYPE)) + _ = IntrinsicCall.create( + intrinsic, + [Reference(sym), ("kind", Literal("1.0", REAL_SINGLE_TYPE))]) assert ("Precision argument to REAL operation must be specified using a " "DataSymbol, ScalarType.PRECISION or integer Literal but got " "xxxx" in str(err.value)) # A Symbol of REAL type cannot be used to specify a precision wrong_kind = DataSymbol("not_wp", REAL_SINGLE_TYPE) with pytest.raises(TypeError) as err: - _ = BinaryOperation.create(oper, Reference(sym), Reference(wrong_kind)) + _ = IntrinsicCall.create( + intrinsic, + [Reference(sym), ("kind", Reference(wrong_kind))]) assert ("If the precision argument to a REAL operation is a Reference " "then it must be to a symbol of integer type but got: 'yyyy'" in str(err.value)) diff --git a/src/psyclone/tests/psyir/symbols/datasymbol_test.py b/src/psyclone/tests/psyir/symbols/datasymbol_test.py index e2819e5429..0b1c235c0d 100644 --- a/src/psyclone/tests/psyir/symbols/datasymbol_test.py +++ b/src/psyclone/tests/psyir/symbols/datasymbol_test.py @@ -265,8 +265,8 @@ def test_datasymbol_initial_value_setter_invalid(): with pytest.raises(ValueError) as error: _ = DataSymbol('a', INTEGER_SINGLE_TYPE, initial_value=ct_expr) assert ("Error setting initial value for symbol 'a'. PSyIR static " - "expressions can only contain PSyIR Literal, Operation, Reference " - "or CodeBlock nodes but found:" in str(error.value)) + "expressions can only contain PSyIR Literal, Operation, Reference," + " IntrinsicCall or CodeBlock nodes but found:" in str(error.value)) with pytest.raises(ValueError) as error: DataSymbol('a', INTEGER_SINGLE_TYPE, interface=ArgumentInterface(), diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index 993f3215a8..f88b9e9c06 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -293,9 +293,9 @@ def test_arraytype(): # TODO #1857: the datatype property might be affected. assert array_type.datatype == scalar_type # Provided and stored as a Literal (DataNode) - assert array_type.shape[1].upper is literal + assert array_type.shape[1].upper == literal # Provided and stored as an Operator (DataNode) - assert array_type.shape[2].upper is var_plus_1 + assert array_type.shape[2].upper == var_plus_1 # Provided and stored as a Reference to a DataSymbol assert isinstance(array_type.shape[3].upper, Reference) assert array_type.shape[3].upper.symbol is data_symbol diff --git a/src/psyclone/tests/psyir/transformations/arrayrange2loop_trans_test.py b/src/psyclone/tests/psyir/transformations/arrayrange2loop_trans_test.py index b77a45ccf0..5985f8c6eb 100644 --- a/src/psyclone/tests/psyir/transformations/arrayrange2loop_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/arrayrange2loop_trans_test.py @@ -40,7 +40,8 @@ import pytest from psyclone.psyir.nodes import Literal, BinaryOperation, Reference, \ - Range, ArrayReference, Assignment, Node, DataNode, KernelSchedule + Range, ArrayReference, Assignment, Node, DataNode, KernelSchedule, \ + IntrinsicCall from psyclone.psyGen import Transformation from psyclone.psyir.symbols import (ArgumentInterface, ArrayType, DataSymbol, INTEGER_TYPE, REAL_TYPE, SymbolTable) @@ -66,12 +67,12 @@ def create_range(array_symbol, dim): ''' int_dim = Literal(str(dim), INTEGER_TYPE) - lbound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(array_symbol), int_dim) - ubound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(array_symbol), int_dim.copy()) + lbound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(array_symbol), ("dim", int_dim)]) + ubound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(array_symbol), ("dim", int_dim.copy())]) return Range.create(lbound, ubound) @@ -370,16 +371,17 @@ def test_same_range(): # y3(n,2:n:2)=x(2:n:2)*z(1,2:n:2)+a(1) @pytest.mark.parametrize("lhs_create,rhs_create,expected", [(create_array_x, create_literal, - " do idx = LBOUND(x, 1), UBOUND(x, 1), 1\n" + " do idx = LBOUND(x, dim=1), UBOUND(x, dim=1), 1\n" " x(idx) = 0.0\n"), (create_array_x, create_array_y, - " do idx = LBOUND(x, 1), UBOUND(x, 1), 1\n" + " do idx = LBOUND(x, dim=1), UBOUND(x, dim=1), 1\n" " x(idx) = y(n,idx)\n"), (create_array_y, create_array_x, - " do idx = LBOUND(y, 2), UBOUND(y, 2), 1\n" + " do idx = LBOUND(y, dim=2), UBOUND(y, dim=2), 1\n" " y(n,idx) = x(idx)\n"), (create_array_y_2d_slice, create_array_z, - " do idx = LBOUND(y2, 2), UBOUND(y2, 2), 1\n" + " do idx = LBOUND(y2, dim=2), UBOUND(y2, dim=2)," + " 1\n" " y2(:,idx) = z(:,n,idx)\n"), (create_array_y_slice_subset, create_expr, " do idx = 2, n, 2\n" @@ -425,8 +427,8 @@ def test_transform_multi_apply(tmpdir): trans.apply(assignment) trans.apply(assignment) expected = ( - " do idx = LBOUND(y2, 2), UBOUND(y2, 2), 1\n" - " do idx_1 = LBOUND(y2, 1), UBOUND(y2, 1), 1\n" + " do idx = LBOUND(y2, dim=2), UBOUND(y2, dim=2), 1\n" + " do idx_1 = LBOUND(y2, dim=1), UBOUND(y2, dim=1), 1\n" " y2(idx_1,idx) = z(idx_1,n,idx)\n" " enddo\n" " enddo\n") @@ -461,10 +463,10 @@ def test_transform_apply_insert(tmpdir): trans.apply(assignment2) writer = FortranWriter() expected = ( - " do idx = LBOUND(x, 1), UBOUND(x, 1), 1\n" + " do idx = LBOUND(x, dim=1), UBOUND(x, dim=1), 1\n" " x(idx) = y(n,idx)\n" " enddo\n" - " do idx_1 = LBOUND(y2, 2), UBOUND(y2, 2), 1\n" + " do idx_1 = LBOUND(y2, dim=2), UBOUND(y2, dim=2), 1\n" " y2(:,idx_1) = z(:,n,idx_1)\n" " enddo\n") result = writer(routine) @@ -561,8 +563,8 @@ def test_validate_intrinsic(): symbol_table = SymbolTable() array_x = create_array_x(symbol_table) array_y_2 = create_array_y_2d_slice(symbol_table) - matmul = BinaryOperation.create(BinaryOperation.Operator.MATMUL, - array_y_2, array_x) + matmul = IntrinsicCall.create(IntrinsicCall.Intrinsic.MATMUL, + [array_y_2, array_x]) reference = ArrayReference.create( symbol_table.lookup("x"), [create_range(symbol_table.lookup("x"), 1)]) assignment = Assignment.create(reference, matmul) @@ -571,9 +573,6 @@ def test_validate_intrinsic(): with pytest.raises(TransformationError) as info: trans.validate(assignment) assert ( - "Error in ArrayRange2LoopTrans transformation. The rhs of the " - "supplied Assignment node 'BinaryOperation[operator:'MATMUL']\n" - "ArrayReference[name:'y2']\nRange[]\nRange[]\n\n" - "ArrayReference[name:'x']\nRange[]\n' contains the " - "MATMUL operator which can't be performed elementwise." in - str(info.value)) + "Error in ArrayRange2LoopTrans transformation. The rhs of the supplied" + " Assignment contains a call 'MATMUL(y2(:,:), x(:))'." + in str(info.value)) diff --git a/src/psyclone/tests/psyir/transformations/hoist_local_arrays_trans_test.py b/src/psyclone/tests/psyir/transformations/hoist_local_arrays_trans_test.py index 011c448594..d65749bad9 100644 --- a/src/psyclone/tests/psyir/transformations/hoist_local_arrays_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/hoist_local_arrays_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2022, Science and Technology Facilities Council. +# Copyright (c) 2022-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,6 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ---------------------------------------------------------------------------- # Author: A. R. Porter, STFC Daresbury Lab +# Modified: S. Siso, STFC Daresbury Lab '''This module tests the hoist local arrays transformation. ''' @@ -136,8 +137,8 @@ def test_apply_multi_dim_imported_limits(fortran_reader, fortran_writer): # We cannot test the compilation of the generated code because of # the 'use some_mod'. assert "real, allocatable, dimension(:,:), private :: a\n" in code - assert (" if (.not.allocated(a) .or. ubound(a, 1) /= jpi .or. " - "ubound(a, 2) /= jpj) then\n" + assert (" if (.not.allocated(a) .or. ubound(a, dim=1) /= jpi .or. " + "ubound(a, dim=2) /= jpj) then\n" " if (allocated(a)) then\n" " deallocate(a)\n" " end if\n" @@ -165,8 +166,8 @@ def test_apply_arg_limits(fortran_reader, fortran_writer, tmpdir): hoist_trans.apply(routine) code = fortran_writer(psyir).lower() assert "real, allocatable, dimension(:,:), private :: a\n" in code - assert (" if (.not.allocated(a) .or. ubound(a, 1) /= nx .or. " - "ubound(a, 2) /= ny) then\n" + assert (" if (.not.allocated(a) .or. ubound(a, dim=1) /= nx .or. " + "ubound(a, dim=2) /= ny) then\n" " if (allocated(a)) then\n" " deallocate(a)\n" " end if\n" @@ -197,16 +198,16 @@ def test_apply_runtime_checks(fortran_reader, fortran_writer, tmpdir): hoist_trans.apply(routine) code = fortran_writer(psyir).lower() assert "real, allocatable, dimension(:,:), private :: a\n" in code - assert (" if (.not.allocated(a) .or. ubound(a, 1) /= nx .or. " - "ubound(a, 2) /= ny) then\n" + assert (" if (.not.allocated(a) .or. ubound(a, dim=1) /= nx .or. " + "ubound(a, dim=2) /= ny) then\n" " if (allocated(a)) then\n" " deallocate(a)\n" " end if\n" " allocate(a(1:nx,1:ny))\n" " end if\n" in code) - assert (" if (.not.allocated(b) .or. lbound(b, 1) /= nx .or. " - "ubound(b, 1) /= ny .or. lbound(b, 2) /= nx .or. " - "ubound(b, 2) /= ny) then\n" + assert (" if (.not.allocated(b) .or. lbound(b, dim=1) /= nx .or. " + "ubound(b, dim=1) /= ny .or. lbound(b, dim=2) /= nx .or. " + "ubound(b, dim=2) /= ny) then\n" " if (allocated(b)) then\n" " deallocate(b)\n" " end if\n" @@ -242,15 +243,15 @@ def test_apply_multi_arrays(fortran_reader, fortran_writer): assert "real, allocatable, dimension(:,:), private :: a" in code assert "integer, allocatable, dimension(:,:), private :: mask" in code assert ( - " if (.not.allocated(mask) .or. ubound(mask, 1) /= jpi .or. " - "ubound(mask, 2) /= jpj) then\n" + " if (.not.allocated(mask) .or. ubound(mask, dim=1) /= jpi .or. " + "ubound(mask, dim=2) /= jpj) then\n" " if (allocated(mask)) then\n" " deallocate(mask)\n" " end if\n" " allocate(mask(1:jpi,1:jpj))\n" " end if\n" - " if (.not.allocated(a) .or. ubound(a, 1) /= nx .or. " - "ubound(a, 2) /= ny) then\n" + " if (.not.allocated(a) .or. ubound(a, dim=1) /= nx .or. " + "ubound(a, dim=2) /= ny) then\n" " if (allocated(a)) then\n" " deallocate(a)\n" " end if\n" @@ -281,8 +282,8 @@ def test_apply_name_clash(fortran_reader, fortran_writer, tmpdir): code = fortran_writer(psyir).lower() assert (" real, allocatable, dimension(:,:), private :: a\n" " real, allocatable, dimension(:,:), private :: a_2\n" in code) - assert (" if (.not.allocated(a_2) .or. ubound(a_2, 1) /= nx .or. " - "ubound(a_2, 2) /= ny) then\n" + assert (" if (.not.allocated(a_2) .or. ubound(a_2, dim=1) /= nx .or. " + "ubound(a_2, dim=2) /= ny) then\n" " if (allocated(a_2)) then\n" " deallocate(a_2)\n" " end if\n" diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index 62740aa2d1..ce19fce50a 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ---------------------------------------------------------------------------- # Author: A. R. Porter, STFC Daresbury Lab -# Modified: R. W. Ford, STFC Daresbury Lab +# Modified: R. W. Ford and S. Siso, STFC Daresbury Lab '''This module tests the inlining transformation. ''' @@ -42,7 +42,7 @@ from psyclone.configuration import Config from psyclone.errors import InternalError -from psyclone.psyir.nodes import Call, IntrinsicCall, Reference, Routine +from psyclone.psyir.nodes import Call, IntrinsicCall, Reference, Routine, Loop from psyclone.psyir.symbols import DataSymbol, DeferredType, AutomaticInterface from psyclone.psyir.transformations import (InlineTrans, TransformationError) @@ -319,7 +319,7 @@ def test_apply_struct_arg(fortran_reader, fortran_writer, tmpdir): f"end module test_mod\n") psyir = fortran_reader.psyir_from_source(code) inline_trans = InlineTrans() - for routine in psyir.walk(Call): + for routine in psyir.walk(Routine)[0].walk(Call, stop_type=Call): inline_trans.apply(routine) output = fortran_writer(psyir) @@ -453,7 +453,7 @@ def test_apply_struct_slice_arg(fortran_reader, fortran_writer, tmpdir): f"end module test_mod\n") psyir = fortran_reader.psyir_from_source(code) inline_trans = InlineTrans() - for routine in psyir.walk(Call): + for routine in psyir.walk(Routine)[0].walk(Call, stop_type=Call): inline_trans.apply(routine) output = fortran_writer(psyir) assert "var_list(:)%local%nx = var_list(:)%local%nx + 1" in output @@ -490,7 +490,7 @@ def test_apply_struct_local_limits_caller(fortran_reader, fortran_writer, f"end module test_mod\n") psyir = fortran_reader.psyir_from_source(code) inline_trans = InlineTrans() - for routine in psyir.walk(Call): + for routine in psyir.walk(Routine)[0].walk(Call, stop_type=Call): inline_trans.apply(routine) output = fortran_writer(psyir) assert "var_list(3:7)%data(2) = 1.0" in output @@ -534,7 +534,7 @@ def test_apply_struct_local_limits_caller_decln(fortran_reader, fortran_writer, f"end module test_mod\n") psyir = fortran_reader.psyir_from_source(code) inline_trans = InlineTrans() - for routine in psyir.walk(Call): + for routine in psyir.walk(Routine)[0].walk(Call, stop_type=Call): inline_trans.apply(routine) output = fortran_writer(psyir) # Actual declared range is non-default. @@ -587,7 +587,7 @@ def test_apply_struct_local_limits_routine(fortran_reader, fortran_writer, f"end module test_mod\n") psyir = fortran_reader.psyir_from_source(code) inline_trans = InlineTrans() - for routine in psyir.walk(Call): + for routine in psyir.walk(Routine)[0].walk(Call, stop_type=Call): inline_trans.apply(routine) output = fortran_writer(psyir) # Access within routine is to full range but formal arg. is declared with @@ -655,7 +655,7 @@ def test_apply_allocatable_array_arg(fortran_reader, fortran_writer): ) psyir = fortran_reader.psyir_from_source(code) inline_trans = InlineTrans() - for routine in psyir.walk(Call): + for routine in psyir.walk(Routine)[0].walk(Call, stop_type=Call): if not isinstance(routine, IntrinsicCall): inline_trans.apply(routine) output = fortran_writer(psyir) @@ -715,7 +715,7 @@ def test_apply_array_slice_arg(fortran_reader, fortran_writer, tmpdir): "end module test_mod\n") psyir = fortran_reader.psyir_from_source(code) inline_trans = InlineTrans() - for call in psyir.walk(Call): + for call in psyir.walk(Routine)[0].walk(Call, stop_type=Call): inline_trans.apply(call) output = fortran_writer(psyir) assert (" do i = 1, 10, 1\n" @@ -763,11 +763,11 @@ def test_apply_struct_array_arg(fortran_reader, fortran_writer, tmpdir): f" end subroutine sub\n" f"end module test_mod\n") psyir = fortran_reader.psyir_from_source(code) - routines = psyir.walk(Call) + loops = psyir.walk(Loop) inline_trans = InlineTrans() - inline_trans.apply(routines[0]) - inline_trans.apply(routines[1]) - inline_trans.apply(routines[2]) + inline_trans.apply(loops[0].loop_body.children[1]) + inline_trans.apply(loops[1].loop_body.children[1]) + inline_trans.apply(loops[2].loop_body.children[1]) output = fortran_writer(psyir).lower() assert (" do i = 1, 10, 1\n" " a(i) = 1.0\n" @@ -821,7 +821,8 @@ def test_apply_struct_array_slice_arg(fortran_reader, fortran_writer, tmpdir): psyir = fortran_reader.psyir_from_source(code) inline_trans = InlineTrans() for call in psyir.walk(Call): - inline_trans.apply(call) + if not isinstance(call, IntrinsicCall): + inline_trans.apply(call) output = fortran_writer(psyir) assert (" do i = 1, 10, 1\n" " a(i) = 1.0\n" @@ -929,7 +930,7 @@ def test_apply_repeated_module_use(fortran_reader, fortran_writer): "end module test_mod\n") psyir = fortran_reader.psyir_from_source(code) inline_trans = InlineTrans() - for call in psyir.walk(Call): + for call in psyir.walk(Routine)[0].walk(Call, stop_type=Call): inline_trans.apply(call) output = fortran_writer(psyir) # Check container symbol has not been renamed. diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/abs2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/abs2code_trans_test.py index 7b0ce078c5..a749a912cd 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/abs2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/abs2code_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -35,13 +35,12 @@ '''Module containing tests for the abs2code transformation.''' -from __future__ import absolute_import import pytest from psyclone.psyir.transformations import Abs2CodeTrans, TransformationError from psyclone.psyir.symbols import SymbolTable, DataSymbol, \ ArgumentInterface, REAL_TYPE -from psyclone.psyir.nodes import Reference, UnaryOperation, Assignment, \ - BinaryOperation, Literal, KernelSchedule +from psyclone.psyir.nodes import Reference, Assignment, \ + BinaryOperation, Literal, KernelSchedule, IntrinsicCall from psyclone.psyir.backend.fortran import FortranWriter from psyclone.configuration import Config from psyclone.tests.utilities import Compile @@ -53,23 +52,21 @@ class is created and that the str and name methods work as expected. ''' trans = Abs2CodeTrans() - assert trans._operator_name == "ABS" - assert trans._classes == (UnaryOperation,) - assert trans._operators == (UnaryOperation.Operator.ABS,) - assert (str(trans) == "Convert the PSyIR ABS intrinsic to equivalent " + assert trans._intrinsic == IntrinsicCall.Intrinsic.ABS + assert (str(trans) == "Convert the PSyIR 'ABS' intrinsic to equivalent " "PSyIR code.") assert trans.name == "Abs2CodeTrans" def example_psyir(create_expression): '''Utility function that creates a PSyIR tree containing an ABS - intrinsic operator and returns the operator. + IntrinsicCall and returns it. :param function create_expression: function used to create the \ - content of the ABS operator. + content of the ABS IntrinsicCall. - :returns: PSyIR ABS operator instance. - :rtype: :py:class:`psyclone.psyir.nodes.UnaryOperation` + :returns: PSyIR ABS IntrinsicCall instance. + :rtype: :py:class:`psyclone.psyir.nodes.IntrinsicCall` ''' symbol_table = SymbolTable() @@ -80,11 +77,11 @@ def example_psyir(create_expression): symbol_table.specify_argument_list([arg1]) var1 = Reference(arg1) var2 = Reference(local) - oper = UnaryOperation.Operator.ABS - operation = UnaryOperation.create(oper, create_expression(var1)) - assign = Assignment.create(var2, operation) + intr = IntrinsicCall.Intrinsic.ABS + intrinsic = IntrinsicCall.create(intr, [create_expression(var1)]) + assign = Assignment.create(var2, intrinsic) _ = KernelSchedule.create("abs_example", symbol_table, [assign]) - return operation + return intrinsic @pytest.mark.parametrize("func,output", @@ -98,8 +95,8 @@ def test_correct(func, output, tmpdir): ''' Config.get().api = "nemo" - operation = example_psyir(func) - root = operation.root + intr_call = example_psyir(func) + root = intr_call.root writer = FortranWriter() result = writer(root) assert ( @@ -109,7 +106,7 @@ def test_correct(func, output, tmpdir): f" psyir_tmp = ABS({output})\n\n" f"end subroutine abs_example\n") in result trans = Abs2CodeTrans() - trans.apply(operation, root.symbol_table) + trans.apply(intr_call, root.symbol_table) result = writer(root) assert ( f"subroutine abs_example(arg)\n" @@ -136,15 +133,15 @@ def test_correct_expr(tmpdir): ''' Config.get().api = "nemo" - operation = example_psyir( + intr_call = example_psyir( lambda arg: BinaryOperation.create( BinaryOperation.Operator.MUL, arg, Literal("3.14", REAL_TYPE))) - root = operation.root - assignment = operation.parent - operation.detach() + root = intr_call.root + assignment = intr_call.parent + intr_call.detach() op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, - Literal("1.0", REAL_TYPE), operation) + Literal("1.0", REAL_TYPE), intr_call) op2 = BinaryOperation.create(BinaryOperation.Operator.ADD, op1, Literal("2.0", REAL_TYPE)) assignment.addchild(op2) @@ -157,7 +154,7 @@ def test_correct_expr(tmpdir): " psyir_tmp = 1.0 + ABS(arg * 3.14) + 2.0\n\n" "end subroutine abs_example\n") in result trans = Abs2CodeTrans() - trans.apply(operation, root.symbol_table) + trans.apply(intr_call, root.symbol_table) result = writer(root) assert ( "subroutine abs_example(arg)\n" @@ -184,17 +181,17 @@ def test_correct_2abs(tmpdir): ''' Config.get().api = "nemo" - operation = example_psyir( + intr_call = example_psyir( lambda arg: BinaryOperation.create( BinaryOperation.Operator.MUL, arg, Literal("3.14", REAL_TYPE))) - root = operation.root - assignment = operation.parent - abs_op = UnaryOperation.create(UnaryOperation.Operator.ABS, - Literal("1.0", REAL_TYPE)) - operation.detach() + root = intr_call.root + assignment = intr_call.parent + intr_call2 = IntrinsicCall.create(IntrinsicCall.Intrinsic.ABS, + [Literal("1.0", REAL_TYPE)]) + intr_call.detach() op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, - operation, abs_op) + intr_call, intr_call2) assignment.addchild(op1) writer = FortranWriter() result = writer(root) @@ -205,8 +202,8 @@ def test_correct_2abs(tmpdir): " psyir_tmp = ABS(arg * 3.14) + ABS(1.0)\n\n" "end subroutine abs_example\n") in result trans = Abs2CodeTrans() - trans.apply(operation, root.symbol_table) - trans.apply(abs_op, root.symbol_table) + trans.apply(intr_call, root.symbol_table) + trans.apply(intr_call2, root.symbol_table) result = writer(root) assert ( "subroutine abs_example(arg)\n" @@ -242,5 +239,5 @@ def test_invalid(): with pytest.raises(TransformationError) as excinfo: trans.apply(None) assert ( - "Error in Abs2CodeTrans transformation. The supplied node argument " - "is not a ABS operator, found 'NoneType'." in str(excinfo.value)) + "Error in Abs2CodeTrans transformation. The supplied node must be an " + "'IntrinsicCall', but found 'NoneType'." in str(excinfo.value)) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/dotproduct2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/dotproduct2code_trans_test.py index cb2cb08a7c..54a18c5a80 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/dotproduct2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/dotproduct2code_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2022, Science and Technology Facilities Council +# Copyright (c) 2022-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,6 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: R. W. Ford, STFC Daresbury Lab +# Modified: S. Siso, STFC Daresbury Lab '''Module containing tests for the DotProduct2CodeTrans transformation. @@ -45,7 +46,7 @@ ''' import pytest -from psyclone.psyir.nodes import BinaryOperation +from psyclone.psyir.nodes import IntrinsicCall from psyclone.psyir.transformations import TransformationError from psyclone.psyir.transformations.intrinsics.dotproduct2code_trans import \ DotProduct2CodeTrans, _get_array_bound @@ -67,10 +68,10 @@ def check_validate(code, expected, fortran_reader): ''' psyir = fortran_reader.psyir_from_source(code) trans = DotProduct2CodeTrans() - for bin_op in psyir.walk(BinaryOperation): - if bin_op.operator == BinaryOperation.Operator.DOT_PRODUCT: + for intrinsic in psyir.walk(IntrinsicCall): + if intrinsic.intrinsic == IntrinsicCall.Intrinsic.DOT_PRODUCT: with pytest.raises(TransformationError) as info: - trans.validate(bin_op) + trans.validate(intrinsic) assert expected in str(info.value) @@ -93,9 +94,9 @@ def check_trans(code, expected, fortran_reader, fortran_writer, tmpdir): ''' psyir = fortran_reader.psyir_from_source(code) trans = DotProduct2CodeTrans() - for bin_op in psyir.walk(BinaryOperation): - if bin_op.operator == BinaryOperation.Operator.DOT_PRODUCT: - trans.apply(bin_op) + for intrinsic in psyir.walk(IntrinsicCall): + if intrinsic.intrinsic == IntrinsicCall.Intrinsic.DOT_PRODUCT: + trans.apply(intrinsic) result = fortran_writer(psyir) assert expected in result assert Compile(tmpdir).string_compiles(result) @@ -117,8 +118,8 @@ def test_bound_explicit(fortran_reader, dim1, dim2): f"result = dot_product(v1,v2)\n" f"end subroutine\n") psyir = fortran_reader.psyir_from_source(code) - dot_product = psyir.walk(BinaryOperation)[0] - assert dot_product.operator == BinaryOperation.Operator.DOT_PRODUCT + dot_product = psyir.walk(IntrinsicCall)[0] + assert dot_product.intrinsic == IntrinsicCall.Intrinsic.DOT_PRODUCT lower, upper, step = _get_array_bound( dot_product.children[0], dot_product.children[1]) assert lower.value == '2' @@ -138,12 +139,12 @@ def test_bound_unknown(fortran_reader, fortran_writer): "result = dot_product(v1,v2)\n" "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) - dot_product = psyir.walk(BinaryOperation)[0] - assert dot_product.operator == BinaryOperation.Operator.DOT_PRODUCT + dot_product = psyir.walk(IntrinsicCall)[0] + assert dot_product.intrinsic == IntrinsicCall.Intrinsic.DOT_PRODUCT lower, upper, step = _get_array_bound( dot_product.children[0], dot_product.children[1]) - assert fortran_writer(lower) == 'LBOUND(v1, 1)' - assert fortran_writer(upper) == 'UBOUND(v1, 1)' + assert 'LBOUND(v1, dim=1)' in fortran_writer(lower) + assert 'UBOUND(v1, dim=1)' in fortran_writer(upper) assert step.value == '1' @@ -157,10 +158,9 @@ def test_initialise(): ''' trans = DotProduct2CodeTrans() - assert trans._operator_name == "DOTPRODUCT" - assert (str(trans) == "Convert the PSyIR DOTPRODUCT intrinsic to " + assert (str(trans) == "Convert the PSyIR 'DOT_PRODUCT' intrinsic to " "equivalent PSyIR code.") - assert trans.name == "Dotproduct2CodeTrans" + assert trans.name == "DotProduct2CodeTrans" # DotProduct2CodeTrans class validate method @@ -173,7 +173,7 @@ def test_validate_super(): trans = DotProduct2CodeTrans() with pytest.raises(TransformationError) as info: trans.validate(None) - assert ("The supplied node argument is not a DOTPRODUCT operator, found " + assert ("The supplied node must be an 'IntrinsicCall', but found " "'NoneType'." in str(info.value)) @@ -314,7 +314,7 @@ def test_apply_calls_validate(): trans = DotProduct2CodeTrans() with pytest.raises(TransformationError) as info: trans.apply(None) - assert ("The supplied node argument is not a DOTPRODUCT operator, found " + assert ("The supplied node must be an 'IntrinsicCall', but found " "'NoneType'." in str(info.value)) @@ -366,7 +366,7 @@ def test_apply_unknown_dims(tmpdir, fortran_reader, fortran_writer): " integer :: i\n" " real(kind=r_def) :: res_dot_product\n\n" " res_dot_product = 0.0\n" - " do i = LBOUND(v1, 1), UBOUND(v1, 1), 1\n" + " do i = LBOUND(v1, dim=1), UBOUND(v1, dim=1), 1\n" " res_dot_product = res_dot_product + v1(i) * v2(i)\n" " enddo\n" " result = res_dot_product\n\n") @@ -415,7 +415,7 @@ def test_apply_array_notation( " integer :: i\n" " real :: res_dot_product\n\n" " res_dot_product = 0.0\n" - " do i = LBOUND(v1, 1), UBOUND(v1, 1), 1\n" + " do i = LBOUND(v1, dim=1), UBOUND(v1, dim=1), 1\n" " res_dot_product = res_dot_product + v1(i) * v2(i)\n" " enddo\n" " result = res_dot_product\n\n") @@ -443,7 +443,7 @@ def test_apply_extra_dims(tmpdir, fortran_reader, fortran_writer, arg1, arg2, f" integer :: i\n" f" real :: res_dot_product\n\n" f" res_dot_product = 0.0\n" - f" do i = LBOUND(v1, 1), UBOUND(v1, 1), 1\n" + f" do i = LBOUND(v1, dim=1), UBOUND(v1, dim=1), 1\n" f" res_dot_product = res_dot_product + v1{res1} * v2{res2}\n" f" enddo\n" f" result = res_dot_product\n\n") @@ -472,7 +472,7 @@ def test_apply_extra_dims_sizes(tmpdir, fortran_reader, fortran_writer, f" integer :: i\n" f" real :: res_dot_product\n\n" f" res_dot_product = 0.0\n" - f" do i = LBOUND(v1, 1), UBOUND(v1, 1), 1\n" + f" do i = LBOUND(v1, dim=1), UBOUND(v1, dim=1), 1\n" f" res_dot_product = res_dot_product + v1{res1} * v2{res2}\n" f" enddo\n" f" result = res_dot_product\n\n") diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/operator2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/intrinsic2code_trans_test.py similarity index 61% rename from src/psyclone/tests/psyir/transformations/intrinsics/operator2code_trans_test.py rename to src/psyclone/tests/psyir/transformations/intrinsics/intrinsic2code_trans_test.py index 8d6b838452..85ce118a51 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/operator2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/intrinsic2code_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,36 +32,36 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: R. W. Ford, STFC Daresbury Laboratory -# Modified: A. R. Porter, STFC Daresbury Laboratory +# Modified: A. R. Porter and S. Siso, STFC Daresbury Laboratory -'''Module containing tests for the operator abstract class which -provides common functionality for the intrinsic operator -transformations (such as MIN, ABS and SIGN).''' +'''Module containing tests for the Intrinsic2CodeTrans abstract class which +provides common functionality for the intrinsic transformations (such as MIN, +ABS and SIGN).''' -from __future__ import absolute_import import pytest from psyclone.psyir.transformations import TransformationError -from psyclone.psyir.transformations.intrinsics.operator2code_trans import \ - Operator2CodeTrans +from psyclone.psyir.transformations.intrinsics.intrinsic2code_trans import ( + Intrinsic2CodeTrans) from psyclone.psyir.symbols import DataSymbol, REAL_TYPE -from psyclone.psyir.nodes import Reference, UnaryOperation, Assignment, Literal +from psyclone.psyir.nodes import ( + Reference, Assignment, Literal, IntrinsicCall) def test_create(): # pylint: disable=abstract-class-instantiated - '''Check that Operator2CodeTrans is abstract.''' + '''Check that Intrinsic2CodeTrans is abstract.''' with pytest.raises(TypeError) as excinfo: - _ = Operator2CodeTrans() + _ = Intrinsic2CodeTrans() msg = str(excinfo.value) # Have to split this check as Python >= 3.10 spots that 'method' # should be singular. - assert ("Can't instantiate abstract class Operator2CodeTrans with " + assert ("Can't instantiate abstract class Intrinsic2CodeTrans with " "abstract method" in msg) assert " apply" in msg -class DummyTrans(Operator2CodeTrans): - '''Dummy transformation class used to test Operator2CodeTrans +class DummyTrans(Intrinsic2CodeTrans): + '''Dummy transformation class used to test Intrinsic2CodeTrans methods.''' # pylint: disable=arguments-differ, no-method-argument def apply(): @@ -72,56 +72,46 @@ def test_init(): '''Check that internal variables are initialised as expected.''' dummy = DummyTrans() - assert dummy._operator_name is None - assert dummy._classes is None - assert dummy._operators is None + assert dummy._intrinsic is None def test_str_name(): '''Check that str and name methods behave as expected.''' dummy = DummyTrans() - # operator_name is usually set by the Transformation's __init__ - # method but set it manually here to avoid creating multiple - # implementations of DummyTrans. - dummy._operator_name = "hello" - assert (str(dummy) == "Convert the PSyIR HELLO intrinsic to equivalent " + dummy._intrinsic = IntrinsicCall.Intrinsic.SUM + assert (str(dummy) == "Convert the PSyIR 'SUM' intrinsic to equivalent " "PSyIR code.") - assert dummy.name == "Hello2CodeTrans" + assert dummy.name == "DummyTrans" def test_validate(): '''Check that the validate method raises exceptions as expected.''' dummy = DummyTrans() - # operator_name, classes and operators are usually set by the - # Transformation's __init__ method but set them manually here to - # avoid creating multiple implementations of DummyTrans. - dummy._operator_name = "hello" - dummy._classes = (UnaryOperation,) - dummy._operators = (UnaryOperation.Operator.ABS,) + dummy._intrinsic = IntrinsicCall.Intrinsic.ABS var = Literal("0.0", REAL_TYPE) - operator = UnaryOperation.create(UnaryOperation.Operator.ABS, var) + intrinsic = IntrinsicCall.create(IntrinsicCall.Intrinsic.ABS, [var]) with pytest.raises(TransformationError) as excinfo: - dummy.validate(operator) + dummy.validate(intrinsic) assert ("This transformation requires the operator to be part of an " "assignment statement, but no such assignment was found." in str(excinfo.value)) reference = Reference(DataSymbol("fred", REAL_TYPE)) - _ = Assignment.create(lhs=reference, rhs=operator) + _ = Assignment.create(lhs=reference, rhs=intrinsic) with pytest.raises(TransformationError) as excinfo: dummy.validate(None) - assert ("The supplied node argument is not a hello operator, found " + assert ("The supplied node must be an 'IntrinsicCall', but found " "'NoneType'." in str(excinfo.value)) with pytest.raises(TransformationError) as excinfo: - dummy.validate(UnaryOperation(UnaryOperation.Operator.SIN, var)) - assert ("Error in Hello2CodeTrans transformation. The supplied node " - "operator is invalid, found 'Operator.SIN', but expected one " - "of '['ABS']'." in str(excinfo.value)) + dummy.validate(IntrinsicCall.create( + IntrinsicCall.Intrinsic.COS, [var.detach()])) + assert ("Error in DummyTrans transformation. The supplied IntrinsicCall " + "must be a 'ABS' but found: 'COS'." in str(excinfo.value)) - dummy.validate(operator) + dummy.validate(intrinsic) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py index 96ccca5b89..1cd9c03d8d 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py @@ -41,7 +41,7 @@ from psyclone.psyir.transformations.intrinsics.matmul2code_trans import \ _create_matrix_ref, _get_array_bound from psyclone.psyir.nodes import BinaryOperation, Literal, ArrayReference, \ - Assignment, Reference, Range, KernelSchedule + Assignment, Reference, Range, KernelSchedule, IntrinsicCall from psyclone.psyir.symbols import DataSymbol, SymbolTable, ArrayType, \ ScalarType, INTEGER_TYPE, REAL_TYPE from psyclone.psyir.backend.fortran import FortranWriter @@ -61,30 +61,36 @@ def create_matmul(): array_type = ArrayType(REAL_TYPE, [5, 10, 15]) mat_symbol = DataSymbol("x", array_type) symbol_table.add(mat_symbol) - lbound1 = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, Reference(mat_symbol), one.copy()) - ubound1 = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, Reference(mat_symbol), one.copy()) + lbound1 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(mat_symbol), ("dim", one.copy())]) + ubound1 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(mat_symbol), ("dim", one.copy())]) my_mat_range1 = Range.create(lbound1, ubound1, one.copy()) - lbound2 = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, Reference(mat_symbol), two.copy()) - ubound2 = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, Reference(mat_symbol), two.copy()) + lbound2 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(mat_symbol), ("dim", two.copy())]) + ubound2 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(mat_symbol), ("dim", two.copy())]) my_mat_range2 = Range.create(lbound2, ubound2, one.copy()) matrix = ArrayReference.create(mat_symbol, [my_mat_range1, my_mat_range2, Reference(index)]) array_type = ArrayType(REAL_TYPE, [10, 20, 10]) vec_symbol = DataSymbol("y", array_type) symbol_table.add(vec_symbol) - lbound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, Reference(vec_symbol), one.copy()) - ubound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, Reference(vec_symbol), one.copy()) + lbound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.LBOUND, + [Reference(vec_symbol), ("dim", one.copy())]) + ubound = IntrinsicCall.create( + IntrinsicCall.Intrinsic.UBOUND, + [Reference(vec_symbol), ("dim", one.copy())]) my_vec_range = Range.create(lbound, ubound, one.copy()) vector = ArrayReference.create(vec_symbol, [my_vec_range, Reference(index), one.copy()]) - matmul = BinaryOperation.create( - BinaryOperation.Operator.MATMUL, matrix, vector) + matmul = IntrinsicCall.create( + IntrinsicCall.Intrinsic.MATMUL, [matrix, vector]) lhs_type = ArrayType(REAL_TYPE, [10]) lhs_symbol = DataSymbol("result", lhs_type) symbol_table.add(lhs_symbol) @@ -210,22 +216,22 @@ def _check_ulbound(lower_bound, upper_bound, step, index): respectively. ''' - assert isinstance(lower_bound, BinaryOperation) - assert lower_bound.operator == BinaryOperation.Operator.LBOUND + assert isinstance(lower_bound, IntrinsicCall) + assert lower_bound.intrinsic == IntrinsicCall.Intrinsic.LBOUND assert isinstance(lower_bound.children[0], Reference) assert lower_bound.children[0].symbol is array_symbol assert isinstance(lower_bound.children[1], Literal) assert (lower_bound.children[1].datatype.intrinsic == ScalarType.Intrinsic.INTEGER) - assert lower_bound.children[1].value == str(index) - assert isinstance(upper_bound, BinaryOperation) - assert upper_bound.operator == BinaryOperation.Operator.UBOUND + assert lower_bound.children[1].value == str(index+1) + assert isinstance(upper_bound, IntrinsicCall) + assert upper_bound.intrinsic == IntrinsicCall.Intrinsic.UBOUND assert isinstance(upper_bound.children[0], Reference) assert upper_bound.children[0].symbol is array_symbol assert isinstance(upper_bound.children[1], Literal) assert (upper_bound.children[1].datatype.intrinsic == ScalarType.Intrinsic.INTEGER) - assert upper_bound.children[1].value == str(index) + assert upper_bound.children[1].value == str(index+1) assert isinstance(step, Literal) assert step.value == "1" assert step.datatype.intrinsic == ScalarType.Intrinsic.INTEGER @@ -262,7 +268,7 @@ def _check_ulbound(lower_bound, upper_bound, step, index): reference = Reference(array_symbol) (lower_bound, upper_bound, step) = _get_array_bound(reference, 0) assert isinstance(lower_bound, Literal) - assert isinstance(upper_bound, BinaryOperation) + assert isinstance(upper_bound, IntrinsicCall) (lower_bound2, upper_bound2, step2) = _get_array_bound(reference, 1) assert lower_bound2 is not lower_bound assert upper_bound2 is not upper_bound @@ -274,8 +280,8 @@ def test_initialise(): ''' trans = Matmul2CodeTrans() - assert (str(trans) == "Convert the PSyIR MATMUL intrinsic to equivalent " - "PSyIR code.") + assert (str(trans) == "Convert the PSyIR 'MATMUL' intrinsic to " + "equivalent PSyIR code.") assert trans.name == "Matmul2CodeTrans" @@ -287,8 +293,8 @@ def test_validate1(): trans = Matmul2CodeTrans() with pytest.raises(TransformationError) as excinfo: trans.validate(None) - assert ("Error in Matmul2CodeTrans transformation. The supplied node " - "argument is not a MATMUL operator, found 'NoneType'." + assert ("Error in Matmul2CodeTrans transformation. The supplied node must " + "be an 'IntrinsicCall', but found 'NoneType'." in str(excinfo.value)) @@ -300,12 +306,11 @@ def test_validate2(): ''' trans = Matmul2CodeTrans() with pytest.raises(TransformationError) as excinfo: - trans.validate(BinaryOperation.create( - BinaryOperation.Operator.ADD, Literal("1.0", REAL_TYPE), - Literal("1.0", REAL_TYPE))) + trans.validate(IntrinsicCall.create( + IntrinsicCall.Intrinsic.SUM, [Literal("1.0", REAL_TYPE)])) assert ("Transformation Error: Error in Matmul2CodeTrans transformation. " - "The supplied node operator is invalid, found 'Operator.ADD', " - "but expected one of '['MATMUL']'." in str(excinfo.value)) + "The supplied IntrinsicCall must be a 'MATMUL' but " + "found: 'SUM'." in str(excinfo.value)) def test_validate3(): @@ -319,8 +324,8 @@ def test_validate3(): array_type = ArrayType(REAL_TYPE, [10, 10]) vector = Reference(DataSymbol("x", vector_type)) array = Reference(DataSymbol("y", array_type)) - matmul = BinaryOperation.create( - BinaryOperation.Operator.MATMUL, array, vector) + matmul = IntrinsicCall.create( + IntrinsicCall.Intrinsic.MATMUL, [array, vector]) with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) assert ("Transformation Error: Error in Matmul2CodeTrans transformation. " @@ -340,8 +345,8 @@ def test_validate4(): array_type = ArrayType(REAL_TYPE, [10, 10]) vector = Reference(DataSymbol("x", vector_type)) array = Reference(DataSymbol("y", array_type)) - matmul = BinaryOperation.create( - BinaryOperation.Operator.MATMUL, array, vector) + matmul = IntrinsicCall.create( + IntrinsicCall.Intrinsic.MATMUL, [array, vector]) rhs = BinaryOperation.create( BinaryOperation.Operator.MUL, matmul, vector.copy()) _ = Assignment.create(array.copy(), rhs) @@ -364,8 +369,8 @@ def test_validate5(): [Literal("10", INTEGER_TYPE)]) mult = BinaryOperation.create( BinaryOperation.Operator.MUL, array.copy(), array.copy()) - matmul = BinaryOperation.create( - BinaryOperation.Operator.MATMUL, mult.copy(), mult.copy()) + matmul = IntrinsicCall.create( + IntrinsicCall.Intrinsic.MATMUL, [mult.copy(), mult.copy()]) _ = Assignment.create(array, matmul) with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) @@ -383,8 +388,8 @@ def test_validate6(): ''' trans = Matmul2CodeTrans() scalar = Reference(DataSymbol("x", REAL_TYPE)) - matmul = BinaryOperation.create( - BinaryOperation.Operator.MATMUL, scalar, scalar.copy()) + matmul = IntrinsicCall.create( + IntrinsicCall.Intrinsic.MATMUL, [scalar, scalar.copy()]) _ = Assignment.create(scalar.copy(), matmul) with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) @@ -422,8 +427,8 @@ def test_validate7(): trans = Matmul2CodeTrans() array_type = ArrayType(REAL_TYPE, [10]) array = Reference(DataSymbol("x", array_type)) - matmul = BinaryOperation.create( - BinaryOperation.Operator.MATMUL, array.copy(), array.copy()) + matmul = IntrinsicCall.create( + IntrinsicCall.Intrinsic.MATMUL, [array.copy(), array.copy()]) _ = Assignment.create(array, matmul) with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) @@ -442,8 +447,8 @@ def test_validate8(): trans = Matmul2CodeTrans() array_type = ArrayType(REAL_TYPE, [10, 10, 10]) array = Reference(DataSymbol("x", array_type)) - matmul = BinaryOperation.create( - BinaryOperation.Operator.MATMUL, array.copy(), array.copy()) + matmul = IntrinsicCall.create( + IntrinsicCall.Intrinsic.MATMUL, [array.copy(), array.copy()]) _ = Assignment.create(array, matmul) with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) @@ -463,8 +468,8 @@ def test_validate9(): array = Reference(DataSymbol("x", array_type)) vector_type = ArrayType(REAL_TYPE, [10, 10, 10]) vector = Reference(DataSymbol("y", vector_type)) - matmul = BinaryOperation.create( - BinaryOperation.Operator.MATMUL, array, vector) + matmul = IntrinsicCall.create( + IntrinsicCall.Intrinsic.MATMUL, [array, vector]) _ = Assignment.create(array.copy(), matmul) with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/max2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/max2code_trans_test.py index 78336e6d20..5fdc224a68 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/max2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/max2code_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2021, Science and Technology Facilities Council +# Copyright (c) 2021-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,12 +32,11 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: R. W. Ford, STFC Daresbury Lab +# Author: S. Siso, STFC Daresbury Lab '''Module containing tests for the Max2CodeTrans transformation.''' -from __future__ import absolute_import - -from psyclone.psyir.nodes import BinaryOperation, NaryOperation +from psyclone.psyir.nodes import BinaryOperation, IntrinsicCall from psyclone.psyir.transformations import Max2CodeTrans from psyclone.psyir.transformations.intrinsics.minormax2code_trans import \ MinOrMax2CodeTrans @@ -50,7 +49,5 @@ def test_initialise(): ''' assert issubclass(Max2CodeTrans, MinOrMax2CodeTrans) trans = Max2CodeTrans() - assert trans._operator_name == "MAX" - assert trans._operators == (BinaryOperation.Operator.MAX, - NaryOperation.Operator.MAX) + assert trans._intrinsic == IntrinsicCall.Intrinsic.MAX assert trans._compare_operator == BinaryOperation.Operator.GT diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py index 8e634538e2..d9c9659c0c 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py @@ -168,7 +168,7 @@ def test_apply(fortran_reader, fortran_writer, tmpdir): " result = maxval_var\n\n" "end subroutine maxval_test\n") psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation + # FileContainer/Routine/Assignment/IntrinsicCall intrinsic_node = psyir.children[0].children[0].children[1] trans = Maxval2CodeTrans() trans.apply(intrinsic_node) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/min2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/min2code_trans_test.py index 64503970fb..29cc510007 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/min2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/min2code_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2021, Science and Technology Facilities Council +# Copyright (c) 2021-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,12 +32,12 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: R. W. Ford, STFC Daresbury Lab +# Modified: S. Siso, STFC Daresbury Lab '''Module containing tests for the Min2CodeTrans transformation.''' -from __future__ import absolute_import -from psyclone.psyir.nodes import BinaryOperation, NaryOperation +from psyclone.psyir.nodes import IntrinsicCall, BinaryOperation from psyclone.psyir.transformations import Min2CodeTrans from psyclone.psyir.transformations.intrinsics.minormax2code_trans import \ MinOrMax2CodeTrans @@ -50,7 +50,5 @@ def test_initialise(): ''' assert issubclass(Min2CodeTrans, MinOrMax2CodeTrans) trans = Min2CodeTrans() - assert trans._operator_name == "MIN" - assert trans._operators == (BinaryOperation.Operator.MIN, - NaryOperation.Operator.MIN) + assert trans._intrinsic == IntrinsicCall.Intrinsic.MIN assert trans._compare_operator == BinaryOperation.Operator.LT diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/minormax2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/minormax2code_trans_test.py index 32ee1209ae..6f68296a8b 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/minormax2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/minormax2code_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -36,17 +36,13 @@ '''Module containing tests for the MinOrMax2Code utility transformation. This transformation is designed to be configured to support either MIN or MAX transformations and should not be called -directly. BinaryOperator tests use the MIN transformation and -NaryOperator tests use the MAX transformation in order to test both -cases. +directly. ''' -from __future__ import absolute_import import pytest -from psyclone.configuration import Config -from psyclone.psyir.nodes import Reference, BinaryOperation, NaryOperation, \ - Assignment, Literal, KernelSchedule +from psyclone.psyir.nodes import Reference, BinaryOperation, \ + Assignment, Literal, KernelSchedule, IntrinsicCall from psyclone.psyir.symbols import SymbolTable, DataSymbol, \ ArgumentInterface, REAL_TYPE from psyclone.psyir.transformations import TransformationError @@ -55,36 +51,26 @@ from psyclone.tests.utilities import Compile -@pytest.fixture(scope="module", autouse=True) -def setup(): - '''Make sure that all tests here use nemo as the API.''' - Config.get().api = "nemo" - yield - Config._instance = None - - def test_initialise(): '''Check that variables are set up as expected when an instance of the class is created and that the str and name methods work as expected. ''' trans = MinOrMax2CodeTrans() - assert trans._classes == (BinaryOperation, NaryOperation) assert trans._compare_operator is None # from parent class - assert trans._operator_name is None - assert trans._operators is None + assert trans._intrinsic is None def example_psyir_binary(create_expression): - '''Utility function that creates a PSyIR tree containing a binary MIN - intrinsic operator and returns the operator. + '''Utility function that creates a PSyIR tree containing a MIN + intrinsic and returns it. :param function create_expression: function used to create the \ - content of the first argument of the MIN operator. + content of the first argument of the MIN intrinsic. - :returns: PSyIR MIN operator instance. - :rtype: :py:class:`psyclone.psyir.nodes.BinaryOperation` + :returns: PSyIR MIN intrinsic instance. + :rtype: :py:class:`psyclone.psyir.nodes.IntrinsicCall` ''' symbol_table = SymbolTable() @@ -99,19 +85,19 @@ def example_psyir_binary(create_expression): var1 = Reference(arg1) var2 = Reference(arg2) var3 = Reference(arg3) - oper = BinaryOperation.Operator.MIN - operation = BinaryOperation.create(oper, create_expression(var1), var2) - assign = Assignment.create(var3, operation) + intr = IntrinsicCall.Intrinsic.MIN + intr_call = IntrinsicCall.create(intr, [create_expression(var1), var2]) + assign = Assignment.create(var3, intr_call) _ = KernelSchedule.create("min_example", symbol_table, [assign]) - return operation + return intr_call def example_psyir_nary(): - '''Utility function that creates a PSyIR tree containing an nary MAX - intrinsic operator and returns the operator. + '''Utility function that creates a PSyIR tree containing a MAX + intrinsic and returns it. - :returns: PSyIR MAX operator instance. - :rtype: :py:class:`psyclone.psyir.nodes.NaryOperation` + :returns: PSyIR MAX intrinsic instance. + :rtype: :py:class:`psyclone.psyir.nodes.IntrinsicCall` ''' symbol_table = SymbolTable() @@ -130,11 +116,11 @@ def example_psyir_nary(): var2 = Reference(arg2) var3 = Reference(arg3) var4 = Reference(arg4) - oper = NaryOperation.Operator.MAX - operation = NaryOperation.create(oper, [var1, var2, var3]) - assign = Assignment.create(var4, operation) + intr = IntrinsicCall.Intrinsic.MAX + intr_call = IntrinsicCall.create(intr, [var1, var2, var3]) + assign = Assignment.create(var4, intr_call) _ = KernelSchedule.create("max_example", symbol_table, [assign]) - return operation + return intr_call @pytest.mark.parametrize("func,output", @@ -148,8 +134,8 @@ def test_correct_binary(func, output, tmpdir, fortran_writer): expression. ''' - operation = example_psyir_binary(func) - root = operation.root + intr_call = example_psyir_binary(func) + root = intr_call.root result = fortran_writer(root) assert ( f"subroutine min_example(arg, arg_1)\n" @@ -160,11 +146,9 @@ def test_correct_binary(func, output, tmpdir, fortran_writer): f"end subroutine min_example\n") in result trans = MinOrMax2CodeTrans() # Configure this transformation to use MIN - trans._operator_name = "MIN" - trans._operators = (BinaryOperation.Operator.MIN, - NaryOperation.Operator.MIN) + trans._intrinsic = IntrinsicCall.Intrinsic.MIN trans._compare_operator = BinaryOperation.Operator.LT - trans.apply(operation) + trans.apply(intr_call) result = fortran_writer(root) assert ( f"subroutine min_example(arg, arg_1)\n" @@ -188,12 +172,12 @@ def test_correct_expr(tmpdir, fortran_writer): is part of an expression. ''' - operation = example_psyir_binary(lambda arg: arg) - root = operation.root - assignment = operation.parent - operation.detach() + intr_call = example_psyir_binary(lambda arg: arg) + root = intr_call.root + assignment = intr_call.parent + intr_call.detach() op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, - Literal("1.0", REAL_TYPE), operation) + Literal("1.0", REAL_TYPE), intr_call) op2 = BinaryOperation.create(BinaryOperation.Operator.ADD, op1, Literal("2.0", REAL_TYPE)) assignment.addchild(op2) @@ -208,11 +192,9 @@ def test_correct_expr(tmpdir, fortran_writer): "end subroutine min_example\n") in result trans = MinOrMax2CodeTrans() # Configure this transformation to use MIN - trans._operator_name = "MIN" - trans._operators = (BinaryOperation.Operator.MIN, - NaryOperation.Operator.MIN) + trans._intrinsic = IntrinsicCall.Intrinsic.MIN trans._compare_operator = BinaryOperation.Operator.LT - trans.apply(operation) + trans.apply(intr_call) result = fortran_writer(root) assert ( "subroutine min_example(arg, arg_1)\n" @@ -236,15 +218,15 @@ def test_correct_2min(tmpdir, fortran_writer): is more than one MIN() in an expression. ''' - operation = example_psyir_binary(lambda arg: arg) - root = operation.root - assignment = operation.parent - operation.detach() - min_op = BinaryOperation.create(BinaryOperation.Operator.MIN, - Literal("1.0", REAL_TYPE), - Literal("2.0", REAL_TYPE)) + intr_call = example_psyir_binary(lambda arg: arg) + root = intr_call.root + assignment = intr_call.parent + intr_call.detach() + intr_call2 = IntrinsicCall.create(IntrinsicCall.Intrinsic.MIN, + [Literal("1.0", REAL_TYPE), + Literal("2.0", REAL_TYPE)]) op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, - min_op, operation) + intr_call2, intr_call) assignment.addchild(op1) result = fortran_writer(root) @@ -257,12 +239,10 @@ def test_correct_2min(tmpdir, fortran_writer): "end subroutine min_example\n") in result trans = MinOrMax2CodeTrans() # Configure this transformation to use MIN - trans._operator_name = "MIN" - trans._operators = (BinaryOperation.Operator.MIN, - NaryOperation.Operator.MIN) + trans._intrinsic = IntrinsicCall.Intrinsic.MIN trans._compare_operator = BinaryOperation.Operator.LT - trans.apply(operation) - trans.apply(min_op) + trans.apply(intr_call) + trans.apply(intr_call2) result = fortran_writer(root) assert ( "subroutine min_example(arg, arg_1)\n" @@ -293,8 +273,8 @@ def test_correct_nary(tmpdir, fortran_writer): output. ''' - operation = example_psyir_nary() - root = operation.root + intr_call = example_psyir_nary() + root = intr_call.root result = fortran_writer(root) assert ( "subroutine max_example(arg, arg_1, arg_2)\n" @@ -306,11 +286,9 @@ def test_correct_nary(tmpdir, fortran_writer): "end subroutine max_example\n") in result trans = MinOrMax2CodeTrans() # Configure this transformation to use MAX - trans._operator_name = "MAX" - trans._operators = (BinaryOperation.Operator.MAX, - NaryOperation.Operator.MAX) + trans._intrinsic = IntrinsicCall.Intrinsic.MAX trans._compare_operator = BinaryOperation.Operator.GT - trans.apply(operation) + trans.apply(intr_call) result = fortran_writer(root) assert ( "subroutine max_example(arg, arg_1, arg_2)\n" @@ -339,12 +317,10 @@ def test_invalid(): called.''' trans = MinOrMax2CodeTrans() # Configure this transformation to use MAX - trans._operator_name = "MAX" - trans._operators = (BinaryOperation.Operator.MAX, - NaryOperation.Operator.MAX) + trans._intrinsic = IntrinsicCall.Intrinsic.MAX trans._compare_operator = BinaryOperation.Operator.GT with pytest.raises(TransformationError) as excinfo: trans.apply(None) assert ( - "Error in Max2CodeTrans transformation. The supplied node argument " - "is not a MAX operator, found 'NoneType'." in str(excinfo.value)) + "Error in MinOrMax2CodeTrans transformation. The supplied node must " + "be an 'IntrinsicCall', but found 'NoneType'." in str(excinfo.value)) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/minval2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/minval2code_trans_test.py index 03517f7feb..a90a101486 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/minval2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/minval2code_trans_test.py @@ -168,7 +168,7 @@ def test_apply(fortran_reader, fortran_writer, tmpdir): " result = minval_var\n\n" "end subroutine minval_test\n") psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation + # FileContainer/Routine/Assignment/IntrinsicCall intrinsic_node = psyir.children[0].children[0].children[1] trans = Minval2CodeTrans() trans.apply(intrinsic_node) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py index abd4aa0774..143fbb55ad 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py @@ -390,8 +390,10 @@ def test_not_assignment(fortran_reader): [("10", "20", "1", "10", "1", "20"), ("n", "m", "1", "n", "1", "m"), ("0:n", "2:m", "0", "n", "2", "m"), - (":", ":", "LBOUND(array, 1)", "UBOUND(array, 1)", - "LBOUND(array, 2)", "UBOUND(array, 2)")]) + (":", ":", "LBOUND(array, dim=1)", + "UBOUND(array, dim=1)", + "LBOUND(array, dim=2)", + "UBOUND(array, dim=2)")]) def test_apply(idim1, idim2, rdim11, rdim12, rdim21, rdim22, fortran_reader, fortran_writer, tmpdir): '''Test that a sum intrinsic as the only term on the rhs of an @@ -473,7 +475,8 @@ def test_apply_dimension_1d(fortran_reader, fortran_writer, tmpdir): " result = value1 + sum(array,dim=1) * value2\n" "end subroutine\n") expected_decl = " real :: sum_var\n" - expected_bounds = " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" + expected_bounds = " do i_0 = LBOUND(array, dim=1), " + expected_bounds += "UBOUND(array, dim=1), 1\n" expected_result = " result = value1 + sum_var * value2\n" psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ @@ -512,7 +515,8 @@ def test_apply_dimension_multid(fortran_reader, fortran_writer, tmpdir): psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ # BinaryOperation(MUL)/IntrinsicCall - node = psyir.walk(IntrinsicCall)[0] + node = [intr for intr in psyir.walk(IntrinsicCall) + if intr.intrinsic == IntrinsicCall.Intrinsic.SUM][0] trans = NamedTestTrans() trans.apply(node) result = fortran_writer(psyir) @@ -537,17 +541,18 @@ def test_apply_dimension_multid_unknown( " result(:,:) = value1 + sum(array,dim=2) * value2\n" "end subroutine\n") expected_decl = ( - " real, dimension(LBOUND(array, 1):UBOUND(array, 1),LBOUND(array, 3):" - "UBOUND(array, 3)) :: sum_var\n") + " real, dimension(LBOUND(array, dim=1):UBOUND(array, dim=1)," + "LBOUND(array, dim=3):UBOUND(array, dim=3)) :: sum_var\n") expected_bounds = ( - " do i_2 = LBOUND(array, 3), UBOUND(array, 3), 1\n" - " do i_1 = LBOUND(array, 2), UBOUND(array, 2), 1\n" - " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n") + " do i_2 = LBOUND(array, dim=3), UBOUND(array, dim=3), 1\n" + " do i_1 = LBOUND(array, dim=2), UBOUND(array, dim=2), 1\n" + " do i_0 = LBOUND(array, dim=1), UBOUND(array, dim=1), 1\n") expected_result = " result(:,:) = value1 + sum_var(:,:) * value2\n\n" psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ # BinaryOperation(MUL)/IntrinsicCall - node = psyir.walk(IntrinsicCall)[0] + node = [intr for intr in psyir.walk(IntrinsicCall) + if intr.intrinsic == IntrinsicCall.Intrinsic.SUM][0] trans = NamedTestTrans() trans.apply(node) result = fortran_writer(psyir) @@ -582,7 +587,8 @@ def test_apply_dimension_multid_range(fortran_reader, fortran_writer, tmpdir): psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ # BinaryOperation(MUL)/IntrinsicCall - node = psyir.walk(IntrinsicCall)[0] + node = [intr for intr in psyir.walk(IntrinsicCall) + if intr.intrinsic == IntrinsicCall.Intrinsic.SUM][0] trans = NamedTestTrans() trans.apply(node) result = fortran_writer(psyir) @@ -728,12 +734,12 @@ def test_allocate_dim(fortran_reader, fortran_writer, tmpdir): " integer :: i_1\n" " integer :: i_2\n\n" " ALLOCATE(a(1:4,1:4,1:4))\n" - " ALLOCATE(sum_var(LBOUND(a, 1):UBOUND(a, 1)," - "LBOUND(a, 3):UBOUND(a, 3)))\n" + " ALLOCATE(sum_var(LBOUND(a, dim=1):UBOUND(a, dim=1)," + "LBOUND(a, dim=3):UBOUND(a, dim=3)))\n" " sum_var(:,:) = 0\n" - " do i_2 = LBOUND(a, 3), UBOUND(a, 3), 1\n" - " do i_1 = LBOUND(a, 2), UBOUND(a, 2), 1\n" - " do i_0 = LBOUND(a, 1), UBOUND(a, 1), 1\n" + " do i_2 = LBOUND(a, dim=3), UBOUND(a, dim=3), 1\n" + " do i_1 = LBOUND(a, dim=2), UBOUND(a, dim=2), 1\n" + " do i_0 = LBOUND(a, dim=1), UBOUND(a, dim=1), 1\n" " sum_var(i_0,i_2) = sum_var(i_0,i_2) + a(i_0,i_1,i_2)\n" " enddo\n" " enddo\n" 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 44e9b49296..4ee1ad344d 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/sign2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/sign2code_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -35,15 +35,13 @@ '''Module containing tests for the sign2code transformation.''' -from __future__ import absolute_import import pytest from psyclone.psyir.transformations import Sign2CodeTrans, TransformationError from psyclone.psyir.symbols import SymbolTable, DataSymbol, \ ArgumentInterface, REAL_TYPE from psyclone.psyir.nodes import Reference, BinaryOperation, Assignment, \ - Literal, KernelSchedule + Literal, KernelSchedule, IntrinsicCall from psyclone.psyir.backend.fortran import FortranWriter -from psyclone.configuration import Config from psyclone.tests.utilities import Compile @@ -53,23 +51,21 @@ class is created and that the str and name methods work as expected. ''' trans = Sign2CodeTrans() - assert trans._operator_name == "SIGN" - assert trans._classes == (BinaryOperation,) - assert trans._operators == (BinaryOperation.Operator.SIGN,) - assert (str(trans) == "Convert the PSyIR SIGN intrinsic to equivalent " + assert trans._intrinsic == IntrinsicCall.Intrinsic.SIGN + assert (str(trans) == "Convert the PSyIR 'SIGN' intrinsic to equivalent " "PSyIR code.") assert trans.name == "Sign2CodeTrans" def example_psyir(create_expression): '''Utility function that creates a PSyIR tree containing a SIGN - intrinsic operator and returns the operator. + intrinsic and returns it. :param function create_expression: function used to create the \ - content of the first argument of the SIGN operator. + content of the first argument of the SIGN intrinsic. - :returns: PSyIR SIGN operator instance. - :rtype: :py:class:`psyclone.psyir.nodes.BinaryOperation` + :returns: PSyIR SIGN intrinsic instance. + :rtype: :py:class:`psyclone.psyir.nodes.IntrinsicCall` ''' symbol_table = SymbolTable() @@ -84,11 +80,11 @@ def example_psyir(create_expression): var1 = Reference(arg1) var2 = Reference(arg2) var3 = Reference(arg3) - oper = BinaryOperation.Operator.SIGN - operation = BinaryOperation.create(oper, create_expression(var1), var2) - assign = Assignment.create(var3, operation) + intr = IntrinsicCall.Intrinsic.SIGN + intr_call = IntrinsicCall.create(intr, [create_expression(var1), var2]) + assign = Assignment.create(var3, intr_call) _ = KernelSchedule.create("sign_example", symbol_table, [assign]) - return operation + return intr_call @pytest.mark.parametrize("func,output", @@ -102,9 +98,8 @@ def test_correct(func, output, tmpdir): expression. ''' - Config.get().api = "nemo" - operation = example_psyir(func) - root = operation.root + intr_call = example_psyir(func) + root = intr_call.root writer = FortranWriter() result = writer(root) assert ( @@ -115,7 +110,7 @@ def test_correct(func, output, tmpdir): f" psyir_tmp = SIGN({output}, arg_1)\n\n" f"end subroutine sign_example\n") in result trans = Sign2CodeTrans() - trans.apply(operation, root.symbol_table) + trans.apply(intr_call, root.symbol_table) result = writer(root) assert ( f"subroutine sign_example(arg, arg_1)\n" @@ -140,8 +135,6 @@ def test_correct(func, output, tmpdir): f" psyir_tmp = res_sign\n\n" f"end subroutine sign_example\n") in result assert Compile(tmpdir).string_compiles(result) - # Remove the created config instance - Config._instance = None def test_correct_expr(tmpdir): @@ -149,16 +142,15 @@ def test_correct_expr(tmpdir): is part of an expression. ''' - Config.get().api = "nemo" - operation = example_psyir( + intr_call = example_psyir( lambda arg: BinaryOperation.create( BinaryOperation.Operator.MUL, arg, Literal("3.14", REAL_TYPE))) - root = operation.root - assignment = operation.parent - operation.detach() + root = intr_call.root + assignment = intr_call.parent + intr_call.detach() op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, - Literal("1.0", REAL_TYPE), operation) + Literal("1.0", REAL_TYPE), intr_call) op2 = BinaryOperation.create(BinaryOperation.Operator.ADD, op1, Literal("2.0", REAL_TYPE)) assignment.addchild(op2) @@ -172,7 +164,7 @@ def test_correct_expr(tmpdir): " psyir_tmp = 1.0 + SIGN(arg * 3.14, arg_1) + 2.0\n\n" "end subroutine sign_example\n") in result trans = Sign2CodeTrans() - trans.apply(operation, root.symbol_table) + trans.apply(intr_call, root.symbol_table) result = writer(root) assert ( "subroutine sign_example(arg, arg_1)\n" @@ -197,8 +189,6 @@ def test_correct_expr(tmpdir): " psyir_tmp = 1.0 + res_sign + 2.0\n\n" "end subroutine sign_example\n") in result assert Compile(tmpdir).string_compiles(result) - # Remove the created config instance - Config._instance = None def test_correct_2sign(tmpdir): @@ -206,19 +196,18 @@ def test_correct_2sign(tmpdir): is more than one SIGN in an expression. ''' - Config.get().api = "nemo" - operation = example_psyir( + intr_call = example_psyir( lambda arg: BinaryOperation.create( BinaryOperation.Operator.MUL, arg, Literal("3.14", REAL_TYPE))) - root = operation.root - assignment = operation.parent - operation.detach() - sign_op = BinaryOperation.create( - BinaryOperation.Operator.SIGN, Literal("1.0", REAL_TYPE), - Literal("1.0", REAL_TYPE)) + root = intr_call.root + assignment = intr_call.parent + intr_call.detach() + intr_call2 = IntrinsicCall.create( + IntrinsicCall.Intrinsic.SIGN, + [Literal("1.0", REAL_TYPE), Literal("1.0", REAL_TYPE)]) op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, - sign_op, operation) + intr_call2, intr_call) assignment.addchild(op1) writer = FortranWriter() result = writer(root) @@ -230,8 +219,8 @@ def test_correct_2sign(tmpdir): " psyir_tmp = SIGN(1.0, 1.0) + SIGN(arg * 3.14, arg_1)\n\n" "end subroutine sign_example\n") in result trans = Sign2CodeTrans() - trans.apply(operation, root.symbol_table) - trans.apply(sign_op, root.symbol_table) + trans.apply(intr_call, root.symbol_table) + trans.apply(intr_call2, root.symbol_table) result = writer(root) assert ( "subroutine sign_example(arg, arg_1)\n" @@ -271,8 +260,6 @@ def test_correct_2sign(tmpdir): " psyir_tmp = res_sign_1 + res_sign\n\n" "end subroutine sign_example\n") in result assert Compile(tmpdir).string_compiles(result) - # Remove the created config instance - Config._instance = None def test_invalid(): @@ -282,5 +269,5 @@ def test_invalid(): with pytest.raises(TransformationError) as excinfo: trans.apply(None) assert ( - "Error in Sign2CodeTrans transformation. The supplied node argument " - "is not a SIGN operator, found 'NoneType'." in str(excinfo.value)) + "Error in Sign2CodeTrans transformation. The supplied node must be " + "an 'IntrinsicCall', but found 'NoneType'." in str(excinfo.value)) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py index 334e3b4c3c..5420951cf5 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py @@ -166,7 +166,7 @@ def test_apply(fortran_reader, fortran_writer, tmpdir): " result = sum_var\n\n" "end subroutine sum_test\n") psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation + # FileContainer/Routine/Assignment/IntrinsicCall intrinsic_node = psyir.children[0].children[0].children[1] trans = Sum2CodeTrans() trans.apply(intrinsic_node) diff --git a/src/psyclone/tests/psyir/transformations/loop_swap_trans_test.py b/src/psyclone/tests/psyir/transformations/loop_swap_trans_test.py index 0e0e9645fb..70aa23fb2e 100644 --- a/src/psyclone/tests/psyir/transformations/loop_swap_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/loop_swap_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2021-2022, Science and Technology Facilities Council. +# Copyright (c) 2021-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -37,10 +37,7 @@ ''' Module containing tests of loop swap transformation.''' -from __future__ import absolute_import - import re - import pytest from psyclone.domain.gocean.transformations import GOceanLoopFuseTrans @@ -202,7 +199,7 @@ def test_loop_swap_validate_loop_type(): def test_loop_swap_validate_nodes_in_loop(fortran_reader): ''' - Tests that loops containing calls or codeblocks are not swapped. + Tests that loops containing impure calls or codeblocks are not swapped. ''' # A dummy program to easily create the PSyIR for the # test cases we need. @@ -224,11 +221,12 @@ def test_loop_swap_validate_nodes_in_loop(fortran_reader): schedule = psyir.children[0] swap = LoopSwapTrans() - # Test that a generic call is not accepted. + # Check with a subroutine which is not guaranteed to be pure with pytest.raises(TransformationError) as err: swap.apply(schedule[0]) assert ("Nodes of type 'Call' cannot be enclosed by a LoopSwapTrans " - "transformation" in str(err.value)) + "unless they can be guaranteed to be pure, but found:" + in str(err.value)) # Make sure the write statement is stored as a code block assert isinstance(schedule[1].loop_body[0].loop_body[0], CodeBlock) diff --git a/src/psyclone/tests/psyir/transformations/loop_tiling_2d_trans_test.py b/src/psyclone/tests/psyir/transformations/loop_tiling_2d_trans_test.py index 2a9e8240f5..ecf2c652e8 100644 --- a/src/psyclone/tests/psyir/transformations/loop_tiling_2d_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/loop_tiling_2d_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2021, Science and Technology Facilities Council. +# Copyright (c) 2021-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -36,7 +36,6 @@ '''This module contains the unit tests for the LoopTiling2DTrans module''' -from __future__ import absolute_import, print_function import pytest from psyclone.psyir.nodes import Loop @@ -128,7 +127,8 @@ def test_loop_tiling_2d_trans_validation3(fortran_reader): with pytest.raises(TransformationError) as err: LoopTiling2DTrans().validate(outer_loop) assert ("Nodes of type 'Call' cannot be enclosed by a LoopSwapTrans " - "transformation" in str(err.value)) + "unless they can be guaranteed to be pure, but found: " + "['call my_sub(i, j)\\n']." in str(err.value)) def test_loop_tiling_2d_trans_validation_options(fortran_reader): diff --git a/src/psyclone/tests/psyir/transformations/reference2arrayrange_trans_test.py b/src/psyclone/tests/psyir/transformations/reference2arrayrange_trans_test.py index 221fbed835..1552241223 100644 --- a/src/psyclone/tests/psyir/transformations/reference2arrayrange_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/reference2arrayrange_trans_test.py @@ -40,7 +40,7 @@ import pytest from psyclone.psyGen import Transformation -from psyclone.psyir.nodes import Reference, Literal, BinaryOperation +from psyclone.psyir.nodes import Reference, Literal, IntrinsicCall from psyclone.psyir.symbols import DataSymbol, REAL_TYPE from psyclone.psyir.transformations import Reference2ArrayRangeTrans, \ TransformationError @@ -100,10 +100,10 @@ def test_get_array_bound(fortran_reader): symbol = node.symbol lower_bound, upper_bound, step = \ Reference2ArrayRangeTrans._get_array_bound(symbol, 0) - assert isinstance(lower_bound, BinaryOperation) - assert lower_bound.operator == BinaryOperation.Operator.LBOUND - assert isinstance(upper_bound, BinaryOperation) - assert upper_bound.operator == BinaryOperation.Operator.UBOUND + assert isinstance(lower_bound, IntrinsicCall) + assert lower_bound.intrinsic == IntrinsicCall.Intrinsic.LBOUND + assert isinstance(upper_bound, IntrinsicCall) + assert upper_bound.intrinsic == IntrinsicCall.Intrinsic.UBOUND assert isinstance(step, Literal) assert step.value == "1" reference = lower_bound.children[0] @@ -192,8 +192,8 @@ def test_multid(fortran_reader, fortran_writer): assert "a(:,:,:) = b(:,:,:) * c(:,:,:)\n" in result -def test_operators(fortran_reader, fortran_writer): - '''Test that references to arrays within operators are transformed to +def test_intrinsics(fortran_reader, fortran_writer): + '''Test that references to arrays within intrinsics are transformed to array slice notation, using dotproduct as the example. ''' @@ -269,10 +269,10 @@ def test_validate_query(fortran_reader): with pytest.raises(TransformationError) as info: trans.validate(reference) assert ("References to arrays within LBOUND, UBOUND or SIZE " - "operators should not be transformed." in str(info.value)) + "intrinsics should not be transformed." in str(info.value)) # Check the references to 'b' in the hidden lbound and ubound - # operators within 'b(:)' do not get modified. + # intrinsics within 'b(:)' do not get modified. assignment = psyir.children[0].children[1] for reference in assignment.walk(Reference): # We want to avoid subclasses such as ArrayReference @@ -281,15 +281,15 @@ def test_validate_query(fortran_reader): with pytest.raises(TransformationError) as info: trans.validate(reference) assert ("References to arrays within LBOUND, UBOUND or SIZE " - "operators should not be transformed." in str(info.value)) + "intrinsics should not be transformed." in str(info.value)) - # Check the reference to 'b' in the size operator does not get modified + # Check the reference to 'b' in the size intrinsics does not get modified assignment = psyir.children[0].children[2] reference = assignment.children[1].children[0] with pytest.raises(TransformationError) as info: trans.validate(reference) assert ("References to arrays within LBOUND, UBOUND or SIZE " - "operators should not be transformed." in str(info.value)) + "intrinsics should not be transformed." in str(info.value)) def test_validate_structure(fortran_reader): diff --git a/src/psyclone/tests/psyir/transformations/replace_induction_variables_trans_test.py b/src/psyclone/tests/psyir/transformations/replace_induction_variables_trans_test.py index 2ccdac27b3..d76460df7f 100644 --- a/src/psyclone/tests/psyir/transformations/replace_induction_variables_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/replace_induction_variables_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2022, Science and Technology Facilities Council. +# Copyright (c) 2022-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,14 +32,15 @@ # POSSIBILITY OF SUCH DAMAGE. # ---------------------------------------------------------------------------- # Author: J. Henrichs, Bureau of Meteorology +# Modified: S. Siso, STFC Daresbury Labs '''This module tests the ReplaceInductionVariablesTrans transformation. ''' import pytest -from psyclone.psyir.nodes import Call, Literal -from psyclone.psyir.symbols import INTEGER_TYPE, RoutineSymbol +from psyclone.psyir.nodes import Literal +from psyclone.psyir.symbols import INTEGER_TYPE from psyclone.psyir.transformations import (ReplaceInductionVariablesTrans, TransformationError) @@ -231,39 +232,29 @@ def test_riv_no_arrays(array_expr, fortran_reader, fortran_writer): # ---------------------------------------------------------------------------- -def test_riv_function_calls(fortran_reader, fortran_writer): +def test_riv_impure_function_calls(fortran_reader, fortran_writer): '''Tests that induction variables that use a function call are - not replaced (since the function might return a different value - on each call). - This tests needs to modify the PSyIR since it appears function calls - are not properly parsed atm. + not replaced unless we can guarantee that the call is pure (since the + function might return a different value on each call). ''' source = '''program test integer i, ic1, ic2 real, dimension(10) :: a do i = 1, 10, 5 - ic1 = i+1 + ic1 = SIN(i) ! Pure function call + ic2 = GET_COMMAND_ARGUMENT(i) ! Impure function call a(ic1) = 1+(ic1+1)*ic1 + a(ic2) = 1+(ic2+1)*ic2 end do end program test''' psyir = fortran_reader.psyir_from_source(source) loop = psyir.children[0].children[0] - - # Create a function call, and use it to change the first - # assignment to ic1 = f() + 1. A 'call' inside an expression - # is a function call: - call = Call(RoutineSymbol("f", INTEGER_TYPE)) - rhs = loop.loop_body.children[0].rhs - rhs.children[0].replace_with(call) - # Make sure that we indeed have a call in the rhs now: - assert isinstance(rhs.children[0], Call) - riv = ReplaceInductionVariablesTrans() riv.apply(loop) - - # Only convert the loop - the moved assignment to ic1 will - # be after the loop, i.e. not part of this string: out = fortran_writer(loop) - assert "ic1 = f() + 1" in out - assert "a(ic1) = 1 + (ic1 + 1) * ic1" in out + + # ic1 has been replaced + assert "a(SIN(i)) = 1 + (SIN(i) + 1) * SIN(i)" in out + # ic2 has NOT been replaced + assert "a(ic2) = 1 + (ic2 + 1) * ic2" in out