Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 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
2301919
Merge branch 'master' into 2302_intrinsic_argument_names
LonelyCat124 Nov 7, 2025
1021c06
Changes for review
LonelyCat124 Nov 7, 2025
99ff7b0
Emphasize arg names must be lower case in definitions of Intrinsics
LonelyCat124 Nov 7, 2025
ce27a54
linting
LonelyCat124 Nov 7, 2025
33ddc80
Test updating the integration
LonelyCat124 Nov 11, 2025
2bbdbc0
try again?
LonelyCat124 Nov 11, 2025
eba7d1e
Revert changes to integration. Fix bug sergi found and add tests
LonelyCat124 Nov 12, 2025
e6276e6
Updated canoncalisation name
LonelyCat124 Nov 12, 2025
fcc9892
linting
LonelyCat124 Nov 12, 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
Copy link
Collaborator

Choose a reason for hiding this comment

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

and reorder arguments to match the specification from the Fortran standard.

I suppose this is a leftover?

If canonicalisation fails, then PSyclone will not ...

I think it would be more interesting to know what it will do, e.g. "PSyclone will produce and error" or "will create a CodeBlock"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, clarified.

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
31 changes: 24 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-named-intrinsic-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,18 @@ 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}
Copy link
Member

Choose a reason for hiding this comment

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

This is the only remaining sticking point. I'm not keen on having two different forms for backend options. Since we now have a nice group, we probably don't need the --backend option way of doing it. We could just have three different flags: --backend_disable_validation, --backend-disable-indentation and --backend-disable-named-intrinsic-args. This is wordy but consistent. We could shorten "backend" to "be" or "BE" perhaps? This is a change to the interface but not a big one and probably not one that affects many people. What do @LonelyCat124 @sergisiso and @hiker think?

Copy link
Collaborator

@sergisiso sergisiso Nov 10, 2025

Choose a reason for hiding this comment

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

I have no problem in moving them to independent flags grouped in the help. But I prefer "backend" rather than the abbreviations. Also "disable-named-intrinsic-args" seems like we are disabling some intrinsics, I would call it "--backend-omit-unneeded-intrinsic-arg-names" (or something shorter if the intent is clear)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I'm happy with those names, I'll fix those.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

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-named-intrinsic-args
By default, the backend names any required arguments to intrinsic calls. This option
disables this feature (in case the processed code has overridden a Fortran intrinsic),
i.e. SUM(arr, mask=maskarr) instead of SUM(array=arr, mask=maskarr).

Basic Use
---------

Expand Down Expand Up @@ -317,6 +325,15 @@ The default behaviour may be changed by adding the
:ref:`configuration file <config-default-section>`. Note that any
command-line setting always takes precedence.

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-named-intrinsic-args`` option must
be passed to PSyclone, else the resultant code may not compile or run correctly.

Automatic Profiling Instrumentation
-----------------------------------

Expand Down
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. This option enables control of that 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
52 changes: 38 additions & 14 deletions src/psyclone/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,13 +493,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 @@ -580,6 +573,32 @@ 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-disable-validation", default=argparse.SUPPRESS,
action="store_true",
help=("Disables validation checks that PSyclone backends perform by "
"default.")
)
backend_group.add_argument(
"--backend-disable-indentation", default=argparse.SUPPRESS,
action="store_true",
help="Disables all indentation in the generated output code."
)
backend_group.add_argument(
"--backend-omit-unneeded-intrinsic-arg-names",
default=argparse.SUPPRESS,
action="store_true",
help="By default, the backend names any required arguments to "
"intrinsic calls. This option disables this feature (in case "
"the processed code has overridden a Fortran intrinsic), "
"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 @@ -631,20 +650,25 @@ def main(arguments):
api = args.psykal_dsl
Config.get().api = api

# Record any intrinsic output format settings.
if "backend_omit_unneeded_intrinsic_arg_names" in args:
# The backend won't attempt to add names to required
# arguments to Fortran intrinsics.
Config.get().backend_intrinsic_named_kwargs = False

# Record any profiling options.
if args.profile:
try:
Profiler.set_options(args.profile, api)
except ValueError as err:
print(f"Invalid profiling option: {err}", file=sys.stderr)
sys.exit(1)
if args.backend:
# A command-line flag overrides the setting in the Config file (if
# any).
if "disable-validation" in args.backend:
Config.get().backend_checks_enabled = False
if "disable-indentation" in args.backend:
Config.get().backend_indentation_disabled = True
# A command-line flag overrides the setting in the Config file (if
# any).
if "backend_disable_validation" in args:
Config.get().backend_checks_enabled = False
if "backend_disable_indentation" in args:
Config.get().backend_indentation_disabled = True

# The Configuration manager checks that the supplied path(s) is/are
# valid so protect with a try
Expand Down
68 changes: 62 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,55 @@ 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
# have argument names added, but we do it here to ensure that
# it is is possible.
node.compute_argument_names()
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.
if node.argument_names[idx]:
args.append(
f"{node.argument_names[idx]}="
f"{self._visit(node.arguments[idx])}"
)
else:
args.append(f"{self._visit(node.arguments[idx])}")
args = ", ".join(args)
except NotImplementedError:
# If the Intrinsic fails to have argument names added, 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 +1802,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: Call) -> 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
29 changes: 18 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,26 @@ def intrinsiccall_node(self, node: IntrinsicCall) -> str:
:returns: the SymPy representation for the Intrinsic.

'''
# Add argument names to the intrinsic
try:
node.compute_argument_names()
except (GenerationError, NotImplementedError) as err:
raise VisitorError(
f"Sympy handler can't handle an IntrinsicCall that "
f"can't have argument names automatically added. Use "
f"explicit argument names instead. "
f"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 +789,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
Loading
Loading