Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
7af3005
Initial changes for IntrinsicCall and named arguments
LonelyCat124 Sep 22, 2025
5dd0720
linting
LonelyCat124 Sep 22, 2025
0a371ca
Added canonicalisation of intrinsics into the frontend
LonelyCat124 Sep 23, 2025
3956c10
More test updates
LonelyCat124 Sep 23, 2025
fed2ccc
linting
LonelyCat124 Sep 23, 2025
19a5877
All but 2 tests now pass, both have behaviour changes to fix
LonelyCat124 Sep 23, 2025
8f3250f
linting
LonelyCat124 Sep 23, 2025
808aa34
Merge branch 'master' into 2302_intrinsic_argument_names
LonelyCat124 Sep 24, 2025
4533878
Fixed remaining issues
LonelyCat124 Sep 25, 2025
808ad04
Coverage fix
LonelyCat124 Sep 26, 2025
893de29
Removed unneccessary canonicalise_minmaxsum routine
LonelyCat124 Sep 26, 2025
c856f7c
linting
LonelyCat124 Sep 26, 2025
976b254
Merge branch 'master' into 2302_intrinsic_argument_names
LonelyCat124 Sep 26, 2025
befe4fa
Merge branch 'master' into 2302_intrinsic_argument_names
LonelyCat124 Sep 26, 2025
a36b037
Removed unneeded function from fparser tests
LonelyCat124 Sep 26, 2025
b6879e0
linting
LonelyCat124 Sep 26, 2025
ba7a2cf
Merge branch 'master' into 2302_intrinsic_argument_names
LonelyCat124 Sep 26, 2025
f5bce2b
Merge branch 'master' into 2302_intrinsic_argument_names
arporter Oct 1, 2025
3d910e7
Merge branch 'master' into 2302_intrinsic_argument_names
LonelyCat124 Oct 2, 2025
a0d5fa6
Fixed new test with intrinsic from master
LonelyCat124 Oct 2, 2025
fa98da6
Changes for first review
LonelyCat124 Oct 3, 2025
a06be59
Documenting canonicalisation and fixing linting incompatibility betwe…
LonelyCat124 Oct 3, 2025
bad2aff
Merge branch 'master' into 2302_intrinsic_argument_names
arporter Oct 6, 2025
7dcdcbf
Changes to address review comments
LonelyCat124 Oct 6, 2025
b530cdc
Removed keyword arguments from FLOAT
LonelyCat124 Oct 7, 2025
18ab68e
Fix for NEMO SIGN overriding
LonelyCat124 Oct 7, 2025
e00e779
Added a TODO for NEMO SIGN
LonelyCat124 Oct 7, 2025
36426fb
Fixed error in SIGN
LonelyCat124 Oct 7, 2025
a8c13b6
linting fixW
LonelyCat124 Oct 7, 2025
67a161b
Fix failing tests
LonelyCat124 Oct 7, 2025
755956e
Merge branch 'master' into 2302_intrinsic_argument_names
LonelyCat124 Oct 7, 2025
d91c9cc
Added argument_by_name to call
LonelyCat124 Oct 7, 2025
4c42db1
Merge branch '2302_intrinsic_argument_names' of github.com:stfc/PSycl…
LonelyCat124 Oct 7, 2025
38b267b
Revert "Fix for NEMO SIGN overriding"
LonelyCat124 Oct 8, 2025
a1c480d
Adds switches to change the output behaviour for intrinsics from the …
LonelyCat124 Oct 8, 2025
ab0225e
Added doc updates
LonelyCat124 Oct 8, 2025
8621281
Merged master
LonelyCat124 Oct 15, 2025
c04f77a
First set of fixes towards review
LonelyCat124 Oct 17, 2025
30af84f
linting issues
LonelyCat124 Oct 17, 2025
f40f5eb
Merge branch 'master' into 2302_intrinsic_argument_names
LonelyCat124 Oct 17, 2025
9fff0bd
Changes for review
LonelyCat124 Oct 20, 2025
fbf5002
Merge branch '2302_intrinsic_argument_names' of github.com:stfc/PSycl…
LonelyCat124 Oct 20, 2025
77cf8a6
Add documentation about overriding intrinsics
LonelyCat124 Oct 20, 2025
75c2e84
Merged master
LonelyCat124 Nov 6, 2025
91ca311
[skip-ci] Initial version of intrinsic call extension
LonelyCat124 Sep 2, 2025
dd21eb5
[skip-ci] Initial version of intrinsic call extension
LonelyCat124 Sep 2, 2025
f5aeddf
[skip-ci] Added more intrisic return types and applied black formatter
LonelyCat124 Sep 2, 2025
b9d24f1
[skip-ci] linting
LonelyCat124 Sep 2, 2025
7118834
Finished intrinsic return types...
LonelyCat124 Sep 3, 2025
a2270bc
Finish failing tests
LonelyCat124 Sep 3, 2025
6404214
Fixed remaining possible coverage
LonelyCat124 Sep 4, 2025
b4326b1
Missing coverage line
LonelyCat124 Sep 4, 2025
7d0f504
Added the ability to change READ access to TYPE_INFO
LonelyCat124 Sep 4, 2025
4285bdb
Linting
LonelyCat124 Sep 4, 2025
b7e0f1c
fix code mistakes
LonelyCat124 Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions doc/developer_guide/psyir.rst
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,16 @@ 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.

``IntrinsicCall`` nodes have a canonicalisation function, that is used
within PSyclone during their creation (via the ``IntrinsicCall.create``
function). This attempts to match the Intrinsic and input arguments to
one of the interfaces for the intrinsic (as some intrinsics have multiple
possible argument interfaces). If the canonicalisation is successful, PSyclone
will convert all of the arguments to be named arguments, and reorder arguments
to match the specification from the Fortran standard. If canonicalisation
fails, then PSyclone will not create an ``IntrinsicCall`` corresponding to
the input. This canonicalisation is required to guarantee correct behaviour
when computing reference_accesses or the return type of an Intrinsic.

IntrinsicCalls, like Calls, have properties to inform if the call is to a
pure, elemental, inquiry (does not touch the first argument data) function
Expand Down
2 changes: 1 addition & 1 deletion doc/developer_guide/transformations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ the ``ParallelLoopTrans`` class for reference):
built upon the ``apply`` definition (e.g. ``LoopTrans`` has
validation used for subclasses, but performs no actions in its newly added
``apply`` method).
3. The ``validate`` method should call the ``validate_options`` method on each of
3. The ``validate`` method should call the ``validate_options`` method on
the keyword arguments and ``**kwargs``. This method should not be called on
the ``options`` dictionary. The ``options`` input should overrule the keyword
arguments when determining options to the apply and validate method.
Expand Down
30 changes: 23 additions & 7 deletions doc/user_guide/psyclone_command.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,17 @@ by the command:

.. parsed-literal::


> psyclone -h
usage: psyclone [-h] [-v] [-c CONFIG] [-s SCRIPT] [--enable-cache] [-l {off,all,output}]
[-p {invokes,routines,kernels}]
[--backend {disable-validation,disable-indentation}] [-o OUTPUT_FILE]
[-api DSL] [-oalg OUTPUT_ALGORITHM_FILE] [-opsy OUTPUT_PSY_FILE]
[-o OUTPUT_FILE] [-api DSL] [-oalg OUTPUT_ALGORITHM_FILE] [-opsy OUTPUT_PSY_FILE]
[-okern OUTPUT_KERNEL_PATH] [-dm] [-nodm]
[--kernel-renaming {multiple,single}]
[--log-level {OFF,DEBUG,INFO,WARNING,ERROR,CRITICAL}] [--log-file LOG_FILE]
[--keep-comments] [--keep-directives] [-I INCLUDE] [-d DIRECTORY]
[--modman-file-ignore IGNORE_PATTERN] [--free-form | --fixed-form]
[--backend {disable-validation,disable-indentation}] [--disable-intrinsic-required-args]
filename

Transform a file using the PSyclone source-to-source Fortran compiler
Expand All @@ -83,11 +84,6 @@ by the command:
to apply line-length limit to output Fortran only.
-p {invokes,routines,kernels}, --profile {invokes,routines,kernels}
add profiling hooks for 'kernels', 'invokes' or 'routines'
--backend {disable-validation,disable-indentation}
options to control the PSyIR backend used for code generation. Use
'disable-validation' to disable the validation checks that are
performed by default. Use 'disable-indentation' to turn off all
indentation in the generated code.
-o OUTPUT_FILE (code-transformation mode) output file
-api DSL, --psykal-dsl DSL
whether to use a PSyKAl DSL (one of ['lfric', 'gocean'])
Expand Down Expand Up @@ -123,6 +119,17 @@ by the command:
--modman-file-ignore IGNORE_PATTERN
Ignore files that contain the specified pattern.

Fortran backend control options.:
These settings control how PSyclone outputs Fortran.

--backend {disable-validation,disable-indentation}
options to control the PSyIR backend used for code generation.
Use 'disable-validation' to disable the validation checks that are performed by
default. Use 'disable-indentation' to turn off all indentation in the generated code.
--disable-intrinsic-required-args
Disables output code containing argument names for an intrinsic's required arguments,
i.e. SUM(arr, mask=maskarr) instead of SUM(array=arr, mask=maskarr).

Basic Use
---------

Expand Down Expand Up @@ -523,3 +530,12 @@ some limitations:

Note that using the ``keep-comments`` option alone means that any comments
that PSyclone interprets as directives will be lost from the input.

Overriding Fortran Intrinsics
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

PSyclone attempts to canonicalise Fortran Intrinsics, which involves adding
argument names to each argument in the ``IntrinsicCall`` PSyIR node. This can
cause problems with code that overrides Fortran intrinsics. To ensure correct
behaviour of the output, the ``--disable-intrinsic-required-args`` option must
be passed to PSyclone, else the resultant code may not run correctly.
26 changes: 26 additions & 0 deletions src/psyclone/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ def __init__(self):
# The Fortran standard that fparser should use
self._fortran_standard = None

# By default, the PSyIR backends output argument names on (most)
# IntrinsicCalls. These two options enable control of tha behaviour.
self._backend_intrinsic_named_kwargs = True

# -------------------------------------------------------------------------
def load(self, config_file=None):
'''Loads a configuration file.
Expand Down Expand Up @@ -771,6 +775,28 @@ def get_constants(self):
'''
return self.api_conf().get_constants()

@property
def backend_intrinsic_named_kwargs(self) -> bool:
'''
:returns: whether the output of intrinsic named arguments is
enabled for required intrinsic arguments.
'''
return self._backend_intrinsic_named_kwargs

@backend_intrinsic_named_kwargs.setter
def backend_intrinsic_named_kwargs(self, output_kwargs: bool) -> None:
'''
Setter for whether the backend should output required argument names
on IntrinsicCalls.

:param output_kwargs: whether to output required argument names.
'''
if not isinstance(output_kwargs, bool):
raise TypeError(f"backend_intrinsic_named_kwargs must be a bool "
f"but found '{type(output_kwargs).__name__}'.")

self._backend_intrinsic_named_kwargs = output_kwargs


# =============================================================================
class BaseConfig:
Expand Down
39 changes: 39 additions & 0 deletions src/psyclone/core/access_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ def change_read_to_write(self):
"which does not have 'READ' access.")
self._access_type = AccessType.WRITE

def change_read_to_type_info(self):
'''This changes the access mode from READ to TYPE_INFO.

:raises InternalError: if the variable does not have READ acccess.
'''
if self._access_type != AccessType.READ:
raise InternalError("Trying to change variable to 'TYPE_INFO' "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see you've just adapted this from the existing one but I think the text could be better. Perhaps "...change variable access from 'READ' to 'TYPE_INFO' but access type is '{self._access_type}'"

"which does not have 'READ' access.")
self._access_type = AccessType.TYPE_INFO

@property
def component_indices(self):
'''
Expand Down Expand Up @@ -323,6 +333,35 @@ def add_access(
'''
self.append(AccessInfo(access_type, node, component_indices))

def change_read_to_type_info(self):
'''This function is used to change a READ into a TYPEINFO.

:raises InternalError: if there is an access that is not READ or
INQUIRY or there is > 1 READ accesses.
'''
read_access = None
for acc in self:

if acc.access_type == AccessType.READ:
if read_access:
raise InternalError(
f"Trying to change variable '{self._signature}' to "
f"'TYPE_INFO' but it has more than one 'READ' access."
)
read_access = acc

elif acc.access_type not in AccessType.non_data_accesses():
raise InternalError(
f"Variable '{self._signature}' has a '{acc.access_type}' "
f"access. change_read_to_type_info() expects only "
f"inquiry accesses and a single 'READ' access.")

if not read_access:
raise InternalError(
f"Trying to change variable '{self._signature}' to "
f"'TYPE_INFO' but it does not have a 'READ' access.")
read_access.change_read_to_type_info()

def change_read_to_write(self):
'''This function is only used when analysing an assignment statement.
The LHS has first all variables identified, which will be READ.
Expand Down
30 changes: 23 additions & 7 deletions src/psyclone/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,13 +487,6 @@ def main(arguments):
parser.add_argument(
'-p', '--profile', action="append", choices=Profiler.SUPPORTED_OPTIONS,
help="add profiling hooks for 'kernels', 'invokes' or 'routines'")
parser.add_argument(
'--backend', dest='backend', action="append",
choices=['disable-validation', 'disable-indentation'],
help=("options to control the PSyIR backend used for code generation. "
"Use 'disable-validation' to disable the validation checks that "
"are performed by default. Use 'disable-indentation' to turn off"
" all indentation in the generated code."))

# Code-transformation mode flags
parser.add_argument('-o', metavar='OUTPUT_FILE',
Expand Down Expand Up @@ -574,6 +567,25 @@ def main(arguments):
"(default is to look at the input file extension)."
)

backend_group = parser.add_argument_group(
"Fortran backend control options.",
"These settings control how PSyclone outputs Fortran. "
)
backend_group.add_argument(
'--backend', dest='backend', action="append",
choices=['disable-validation', 'disable-indentation'],
help=("options to control the PSyIR backend used for code generation. "
"Use 'disable-validation' to disable the validation checks that "
"are performed by default. Use 'disable-indentation' to turn off"
" all indentation in the generated code."))
backend_group.add_argument(
"--disable-intrinsic-required-args", default=argparse.SUPPRESS,
action="store_true",
help="Disables output code containing argument names for an "
"intrinsic's required arguments, i.e. SUM(arr, mask=maskarr) "
"instead of SUM(array=arr, mask=maskarr)."
)

args = parser.parse_args(arguments)

# Set the logging system up.
Expand Down Expand Up @@ -626,6 +638,10 @@ def main(arguments):
api = args.psykal_dsl
Config.get().api = api

# Record any intrinsic output format settings.
if "disable_intrinsic_required_args" in args:
Config.get().backend_intrinsic_named_kwargs = False

# Record any profiling options.
if args.profile:
try:
Expand Down
65 changes: 59 additions & 6 deletions src/psyclone/psyir/backend/fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from a PSyIR tree. '''

# pylint: disable=too-many-lines
from psyclone.configuration import Config
from psyclone.errors import InternalError
from psyclone.psyir.backend.language_writer import LanguageWriter
from psyclone.psyir.backend.visitor import VisitorError
Expand Down Expand Up @@ -1739,17 +1740,52 @@ def _gen_arguments(self, node):
result_list.append(self._visit(child))
return ", ".join(result_list)

def call_node(self, node) -> str:
'''Translate the PSyIR call node to Fortran.
def intrinsiccall_node(self, node: IntrinsicCall) -> str:
'''Translate the PSyIR IntrinsicCall node to Fortran.

:param node: a Call PSyIR node.
:type node: :py:class:`psyclone.psyir.nodes.Call`
:param node: an IntrinsicCall PSyIR node.

:returns: the equivalent Fortran code.

'''
args = self._gen_arguments(node)
if isinstance(node, IntrinsicCall) and node.routine.name not in [
# Check the config to determine if we're outputting all argument
# names.
if not Config.get().backend_intrinsic_named_kwargs:
# Config says to avoid outputting argument names where
# possible.
try:
# Canonicalisation handles any error checking we might
# otherwise want to try. Most IntrinsicCalls should already
# be canonicalised, but we do it here to ensure that it is
# is possible.
node.canonicalise()
intrinsic_interface = node._find_matching_interface()
args = []
correct_names = True
for idx, arg_name in enumerate(node.argument_names):
if idx < len(intrinsic_interface) and correct_names:
# This is a potential required argument.
if arg_name == intrinsic_interface[idx]:
args.append(self._visit(node.arguments[idx]))
continue
# Otherwise it didn't match, so we can't remove any
# more argument names, and fall back to the default
# behaviour from here.
correct_names = False
# Otherwise, use the default behaviour.
args.append(
f"{node.argument_names[idx]}="
f"{self._visit(node.arguments[idx])}"
)
args = ", ".join(args)
except NotImplementedError:
# If the Intrinsic fails to canonicalise, or to match
# to an interface, then use the default behaviour.
args = self._gen_arguments(node)
else:
args = self._gen_arguments(node)

if node.routine.name not in [
"DATE_AND_TIME", "SYSTEM_CLOCK", "MVBITS", "RANDOM_NUMBER",
"RANDOM_SEED"]:
# Most intrinsics are functions and so don't have 'call'.
Expand All @@ -1763,6 +1799,23 @@ def call_node(self, node) -> str:
# Otherwise it is inside-expression function call
return f"{self._visit(node.routine)}({args})"

def call_node(self, node) -> str:
'''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.

'''
args = self._gen_arguments(node)

if not node.parent or isinstance(node.parent, Schedule):
return f"{self._nindent}call {self._visit(node.routine)}({args})\n"

# Otherwise it is inside-expression function call
return f"{self._visit(node.routine)}({args})"

def kernelfunctor_node(self, node):
'''
Translate the Kernel functor into Fortran.
Expand Down
28 changes: 17 additions & 11 deletions src/psyclone/psyir/backend/sympy_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

from psyclone.core import (Signature, AccessSequence,
VariablesAccessMap)
from psyclone.errors import GenerationError
from psyclone.psyir.backend.fortran import FortranWriter
from psyclone.psyir.backend.visitor import VisitorError
from psyclone.psyir.frontend.sympy_reader import SymPyReader
Expand Down Expand Up @@ -759,20 +760,25 @@ def intrinsiccall_node(self, node: IntrinsicCall) -> str:
:returns: the SymPy representation for the Intrinsic.

'''
# Force canonicalisation of the intrinsic
try:
node.canonicalise()
except (GenerationError, NotImplementedError) as err:
raise VisitorError(
f"Sympy handler can't handle an IntrinsicCall that "
f"can't be canonicalised. Use explicit argument names "
f"to force canonicalisation. Failing node was "
f"'{node.debug_string()}'.") from err

# 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]
if node.parent:
parent = node.parent.copy()
node = parent.children[node.position]
else:
node = node.copy()
for idx in range(len(node.argument_names)):
# pylint: disable=protected-access
node._argument_names[idx] = (node._argument_names[idx][0],
Expand All @@ -782,7 +788,7 @@ def intrinsiccall_node(self, node: IntrinsicCall) -> str:
args = self._gen_arguments(node)
return f"{self._nindent}{name}({args})"
except KeyError:
return super().call_node(node)
return super().intrinsiccall_node(node)

# -------------------------------------------------------------------------
def reference_node(self, node: Reference) -> str:
Expand Down
2 changes: 0 additions & 2 deletions src/psyclone/psyir/backend/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,6 @@ def _visit(self, node):
# Check global constraints for this node (if validation enabled).
if self._validate_nodes:
node.validate_global_constraints()

# Make a list of the node's ancestor classes (including
# itself) in method resolution order (mro), apart from the
# base "object" class.
Expand All @@ -275,7 +274,6 @@ def _visit(self, node):
for method_name in possible_method_names:
try:
node_result = getattr(self, method_name)(node)

# We can only proceed to add comments if the Visitor
# returned a string, otherwise we just return
if not isinstance(node_result, str):
Expand Down
Loading
Loading