Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions src/psyclone/psyir/backend/fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,20 @@ def literal_node(self, node):

return result

def arrayconstructor_node(self, node):
'''This method is called when an ArrayConstructor instance is
found in the PSyIR tree.

:param node: an ArrayConstructor PSyIR node.
:type node: :py:class:`psyclone.psyir.nodes.ArrayConstructor`

:returns: the Fortran code as a string.
:rtype: str

'''
contents = ", ".join([self._visit(child) for child in node.children])
return "[" + contents + "]"

def ifblock_node(self, node):
'''This method is called when an IfBlock instance is found in the
PSyIR tree.
Expand Down
39 changes: 39 additions & 0 deletions src/psyclone/psyir/frontend/fparser2.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
from psyclone.errors import InternalError, GenerationError
from psyclone.psyir.commentable_mixin import CommentableMixin
from psyclone.psyir.nodes import (
ArrayConstructor,
ArrayMember, ACCRoutineDirective, ArrayOfStructuresReference,
ArrayReference, Assignment, BinaryOperation, Call, CodeBlock, Container,
DataNode, Directive, FileContainer, IfBlock, IntrinsicCall, Literal, Loop,
Expand Down Expand Up @@ -991,6 +992,7 @@ def __init__(
Fortran2003.Allocate_Stmt: self._allocate_handler,
Fortran2003.Allocate_Shape_Spec: self._allocate_shape_spec_handler,
Fortran2003.Assignment_Stmt: self._assignment_handler,
Fortran2003.Array_Constructor: self._array_constructor_handler,
Fortran2003.Structure_Constructor: self._call_handler,
Fortran2003.Data_Pointer_Object: self._structure_accessor_handler,
Fortran2003.Data_Ref: self._structure_accessor_handler,
Expand Down Expand Up @@ -4975,6 +4977,43 @@ def _assignment_handler(self, node, parent):

return assignment

def _array_constructor_handler(self, node, parent):
'''
Transforms an fparser2 Array_Constructor to the PSyIR representation.

:param node: node in fparser2 AST.
:type node: :py:class:`fparser.two.Fortran2003.Array_Constructor`
: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.ArrayConstructor`

:raises NotImplementedError: if the parse tree contains unsupported
elements.
'''
ac_spec = node.items[1]
if isinstance(ac_spec, Fortran2003.Ac_Value_List):
elems = node.items[1].items
# Check that no elements are 'Ac_Implied_Do'
for elem in elems:
if isinstance(elem, Fortran2003.Ac_Implied_Do):
raise NotImplementedError(
"Array constructors with implied do loops cannot be "
"handled in the PSyIR")
array_cons = ArrayConstructor(ast=node, parent=parent)
self.process_nodes(parent=array_cons, nodes=elems)
return array_cons
elif isinstance(ac_spec, Fortran2003.Ac_Spec):
raise NotImplementedError(
"Array constructors with type specifications cannot be "
"handled in the PSyIR")
else: # pragma: no cover
# This should never be reached, but we defensively raise
# an exception just in case.
raise NotImplementedError(
"Unexpected array constructor form encountered")

def _structure_accessor_handler(self, node, parent):
'''
Create the PSyIR for structure accessors found in fparser2 Data_Ref,
Expand Down
2 changes: 2 additions & 0 deletions src/psyclone/psyir/nodes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

''' PSyIR nodes package module '''

from psyclone.psyir.nodes.array_constructor import ArrayConstructor
from psyclone.psyir.nodes.acc_clauses import (
ACCAsyncQueueClause, ACCCopyClause, ACCCopyInClause,
ACCCopyOutClause)
Expand Down Expand Up @@ -113,6 +114,7 @@
# this package e.g. 'from psyclone.psyir.nodes import Literal'
__all__ = [
'colored',
'ArrayConstructor',
'ArrayMember',
'ArrayReference',
'ArrayOfStructuresMember',
Expand Down
153 changes: 153 additions & 0 deletions src/psyclone/psyir/nodes/array_constructor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# -----------------------------------------------------------------------------
# BSD 3-Clause License
#
# Copyright (c) 2017-2026, 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.
# -----------------------------------------------------------------------------
# Authors M. Naylor, University of Cambridge, UK
# -----------------------------------------------------------------------------

''' This module contains the ArrayConstructor node implementation.'''

from psyclone.core import VariablesAccessMap
from psyclone.psyir.symbols import (
Symbol, UnresolvedType, ScalarType, ArrayType, DataTypeSymbol)
from psyclone.psyir.nodes.datanode import DataNode


class ArrayConstructor(DataNode):
'''
Node representing an array constructor.
'''

# Textual description of the node.
_children_valid_format = "[DataNode]*"
_text_name = "ArrayConstructor"
_colour = "yellow"

def __init__(self, **kwargs):
super().__init__(**kwargs)

@staticmethod
def create(*elems: DataNode):
'''Create and ArrayConstructor instance representing an array
with the given elements.

:param elems: the elements of the array being constructed.
:returns: an ArrayConstructor instance.
:rtype: :py:class:`psyclone.psyir.nodes.IfBlock`

:raises GenerationError: if the arguments are not of the \
expected type.
'''
array_cons = ArrayConstructor()
for elem in elems:
array_cons.children.append(elem)
return array_cons

@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
'''
return isinstance(child, DataNode)

@property
def datatype(self):
'''
:returns: the type of this array constructor.
:rtype: :py:class:`psyclone.psyir.symbols.DataType`
'''
# The result of an array constructor is always a rank-1 array.
# We look through the children to find the array-element type.
elem_type = UnresolvedType()
for child in self.children:
if isinstance(child.datatype, ArrayType):
elem_type = child.datatype.elemental_type
break
elif isinstance(child.datatype, ScalarType):
elem_type = child.datatype
break
elif isinstance(child.datatype, DataTypeSymbol):
elem_type = child.datatype
break
return ArrayType(elem_type, [ArrayType.Extent.ATTRIBUTE])

def node_str(self, colour=True):
'''
Construct a text representation of this node, optionally containing
colour control codes.

:param bool colour: whether or not to include colour control codes.

:returns: description of this PSyIR node.
:rtype: str
'''
return f"{self.coloured_name(colour)}[]"

def get_all_accessed_symbols(self) -> set[Symbol]:
'''
:returns: a set of all the symbols accessed inside this
ArrayConstructor.
'''
return super().get_all_accessed_symbols()

def reference_accesses(self) -> VariablesAccessMap:
'''
:returns: a map of all the symbol accessed inside this node, the
keys are Signatures (unique identifiers to a symbol and its
structure accessors) and the values are AccessSequence
(a sequence of AccessTypes).
'''
return super().reference_accesses()

def replace_symbols_using(self, table_or_symbol):
'''
Replace any Symbols referred to by this object with those in the
supplied SymbolTable (or just the supplied Symbol instance) if they
have matching names. If there is no match for a given Symbol then it
is left unchanged.

:param table_or_symbol: the symbol table from which to get replacement
symbols or a single, replacement Symbol.
:type table_or_symbol: :py:class:`psyclone.psyir.symbols.SymbolTable` |
:py:class:`psyclone.psyir.symbols.Symbol`

'''
super().replace_symbols_using(table_or_symbol)


# For AutoAPI documentation generation
__all__ = ['ArrayConstructor']
5 changes: 3 additions & 2 deletions src/psyclone/psyir/symbols/datasymbol.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ def initial_value(self, new_value):
# pylint: disable=import-outside-toplevel
from psyclone.psyir.nodes import (
Assignment, Node, Literal, Operation, Reference,
CodeBlock, IntrinsicCall)
CodeBlock, IntrinsicCall, ArrayConstructor)
from psyclone.psyir.symbols.datatypes import (ScalarType, ArrayType,
UnsupportedType)

Expand All @@ -239,7 +239,8 @@ 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, IntrinsicCall)):
CodeBlock, IntrinsicCall,
ArrayConstructor)):
raise ValueError(
f"Error setting initial value for symbol "
f"'{self.name}'. PSyIR static expressions can only"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
from psyclone.psyGen import Transformation
from psyclone.psyir.nodes import (
Assignment, Call, IntrinsicCall, Loop, Literal, Node, Range, Reference,
CodeBlock, Routine, BinaryOperation)
CodeBlock, Routine, BinaryOperation, ArrayConstructor)
from psyclone.psyir.nodes.array_mixin import ArrayMixin
from psyclone.psyir.nodes.structure_accessor_mixin import (
StructureAccessorMixin)
Expand Down Expand Up @@ -245,6 +245,17 @@ def validate(
raise TransformationError(LazyString(
lambda: f"{message}, but found:\n{node.debug_string()}"))

# Do not allow to transform expressions with ArrayConstructors
# TODO: this could be relaxed in future
if node.has_descendant(ArrayConstructor):
message = (f"{self.name} does not support array assignments that"
" contain an ArrayConstructor anywhere in"
" the expression")
if verbose:
node.append_preceding_comment(message)
raise TransformationError(LazyString(
lambda: f"{message}, but found:\n{node.debug_string()}"))

try:
node.scope
except SymbolError:
Expand Down
2 changes: 1 addition & 1 deletion src/psyclone/tests/core/symbolic_maths_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ def test_symbolic_maths_never_equal_error(fortran_reader):
source = (
"program test_prog\n"
" integer :: a(2)\n"
" a(:) = (/1, 2/)\n"
" a(:) = (/integer :: 1, 2/)\n"
"end program test_prog\n")
psyir = fortran_reader.psyir_from_source(source)
assignment = psyir.children[0][0]
Expand Down
2 changes: 1 addition & 1 deletion src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def test_gen_param_decls_case_insensitive(fortran_reader,
"integer(kind=an_int), parameter :: a_second_int = 5_i_def\n"
"integer, parameter :: nfieldnames3d = 4\n"
"integer, dimension(nfieldnames3d), parameter :: "
"interpolationlevels = [2, 0, HUGE(InterpolationLevels) / 3, 0]\n")
"InterpolationLevels = [2, 0, HUGE(InterpolationLevels) / 3, 0]\n")


def test_gen_decls(fortran_writer):
Expand Down
23 changes: 19 additions & 4 deletions src/psyclone/tests/psyir/backend/fortran_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
ArrayReference, ArrayOfStructuresReference, Range, StructureReference,
Schedule, Routine, Return, FileContainer, IfBlock, OMPTaskloopDirective,
OMPMasterDirective, OMPParallelDirective, Loop, OMPNumTasksClause,
OMPDependClause, IntrinsicCall, OMPReductionClause, UnknownDirective)
OMPDependClause, IntrinsicCall, OMPReductionClause, UnknownDirective,
ArrayConstructor)
from psyclone.psyir.symbols import (
ArgumentInterface, ContainerSymbol, DataSymbol, GenericInterfaceSymbol,
ImportInterface, RoutineSymbol, StaticInterface, Symbol, SymbolTable,
Expand Down Expand Up @@ -1742,7 +1743,7 @@ def test_fw_codeblock_2(fortran_reader, fortran_writer, tmpdir):
'''Check the FortranWriter class codeblock method correctly prints out
the Fortran representation when there is a code block that is part
of a line (not a whole line). In this case the data initialisation
of the array 'a' "(/ 0.0 /)" is a code block.
of the array 'a' "(/ real :: 0.0 /)" is a code block.

'''
# Generate fparser2 parse tree from Fortran code.
Expand All @@ -1751,7 +1752,7 @@ def test_fw_codeblock_2(fortran_reader, fortran_writer, tmpdir):
"contains\n"
"subroutine tmp()\n"
" real a(1)\n"
" a = (/ 0.0 /)\n"
" a = (/ real :: 0.0 /)\n"
"end subroutine tmp\n"
"end module test")
psyir = fortran_reader.psyir_from_source(code)
Expand All @@ -1761,7 +1762,7 @@ def test_fw_codeblock_2(fortran_reader, fortran_writer, tmpdir):

# Generate Fortran from the PSyIR
result = fortran_writer(psyir)
assert "a = (/0.0/)" in result
assert "a = (/REAL :: 0.0/)" in result
assert Compile(tmpdir).string_compiles(result)


Expand Down Expand Up @@ -2351,3 +2352,17 @@ def test_fw_unknowndirective(fortran_writer):
assert fortran_writer(direc) == "!$omp atomic\n"
direc = UnknownDirective(" IVDEP", "DIR")
assert fortran_writer(direc) == "!DIR$ IVDEP\n"


def test_array_constructor(fortran_writer):
'''
Test that the ArrayConstructor visitor generates the expected string.
'''
array_cons = ArrayConstructor.create(
Literal("1", ScalarType.integer_type()),
Literal("2", ScalarType.integer_type()),
ArrayConstructor.create(
Literal("3", ScalarType.integer_type()),
Literal("4", ScalarType.integer_type())))
output = fortran_writer(array_cons)
assert output == "[1, 2, [3, 4]]"
Loading
Loading