Skip to content
Merged
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
3 changes: 3 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
38) PR #3009 for #2981. Prevent ProfilingTrans to add callipers in regions
with potential control flow jumps.

37) PR #3029 for #3026. Fixes a bug with RegionTrans.get_node_list if a
list containing a single Schedule was provided.

Expand Down
23 changes: 23 additions & 0 deletions src/psyclone/psyir/nodes/codeblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,26 @@ def reference_accesses(self) -> VariablesAccessMap:

def __str__(self):
return f"CodeBlock[{len(self._fp2_nodes)} nodes]"

def has_potential_control_flow_jump(self) -> bool:
'''
:returns: whether this CodeBlock contains a potential control flow
jump, e.g. GOTO, EXIT or a labeled statement.
'''
# Loop over the fp2_nodes and check if any are GOTO, EXIT or
# labelled statements
for node in self._fp2_nodes:
for child in walk(node, (Fortran2003.Goto_Stmt,
Fortran2003.Exit_Stmt,
Fortran2003.Cycle_Stmt,
Fortran2003.StmtBase)):
if isinstance(child,
(Fortran2003.Goto_Stmt,
Fortran2003.Exit_Stmt,
Fortran2003.Cycle_Stmt)):
return True
# Also can't support Labelled statements.
if isinstance(child, Fortran2003.StmtBase):
if child.item and child.item.label:
return True
return False
51 changes: 50 additions & 1 deletion src/psyclone/psyir/transformations/profile_trans.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@
# -----------------------------------------------------------------------------
# Author J. Henrichs, Bureau of Meteorology
# Modified by R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab
# Modified by A. B. G. Chalk, STFC Daresbury Lab
# -----------------------------------------------------------------------------

'''This module provides the Profile transformation.
'''

from psyclone.psyir.nodes import Return, ProfileNode
from psyclone.psyir.transformations import TransformationError
from psyclone.psyir.nodes import CodeBlock, ProfileNode, Return, Routine
from psyclone.psyir.transformations.psy_data_trans import PSyDataTrans


Expand Down Expand Up @@ -75,3 +77,50 @@ class ProfileTrans(PSyDataTrans):

def __init__(self):
super().__init__(ProfileNode)

def validate(self, nodes, options=None):
'''
Checks that the supplied list of nodes is valid for profiling
callipers.

:param nodes: a node or list of nodes to be instrumented with
profiling.
:type nodes: :py:class:`psyclone.psyir.nodes.Node` or
list[:py:class:`psyclone.psyir.nodes.Node`]
:param bool options["force"]: whether to ignore potential control
flow jumps when applying this
transformation. Default is False.
Comment thread
sergisiso marked this conversation as resolved.

:raises TransformationError: if the supplied region contains a
potential control flow jump that could
result in skipping the end of profiling
caliper, e.g. EXIT or GOTO.
'''
if not options:
options = {}
forced = options.get("force", False)
super().validate(nodes, options)
if forced:
return
node_list = self.get_node_list(nodes)
# If the node_list is the same as a whole routine then we skip the
# checks for internal control flow jumps.
parent = node_list[0].parent
if (isinstance(parent, Routine) and
len(parent.children) == len(node_list)):
# If the node_list is the same size and the parent of the first
# is the routine then this is the full Routine (see
# RegionDirective.validate for the validation).
return

# Find all the codeblocks and check if they contain a control
# flow jump.
for node in node_list:
codeblocks = node.walk(CodeBlock)
for block in codeblocks:
if block.has_potential_control_flow_jump():
raise TransformationError(
f"Cannot apply the ProfileTrans to a code region "
f"containing a potential control flow jump, as these "
f"could skip the end of profiling caliper. "
f"Found:\n'{block.debug_string()}'")
27 changes: 27 additions & 0 deletions src/psyclone/tests/psyir/nodes/codeblock_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,30 @@ def test_codeblock_equality(parser):
prog = parser(reader)
block4 = CodeBlock(prog.children, CodeBlock.Structure.STATEMENT)
assert block != block4


def test_codeblock_has_potential_control_flow_jump(fortran_reader):
"""Test the has_potential_control_flow_jump function of the CodeBlock
class."""

code = """subroutine test()
integer :: i
GOTO 1234
i = 1
write(*,*) "Hello"
do i = 1, 100
EXIT
end do
1234 i = 3
end subroutine"""
psyir = fortran_reader.psyir_from_source(code)
codeblocks = psyir.walk(CodeBlock)

# GOTO statement
assert codeblocks[0].has_potential_control_flow_jump()
# Write statement
assert not codeblocks[1].has_potential_control_flow_jump()
# Exit statement
assert codeblocks[2].has_potential_control_flow_jump()
# labelled statement
assert codeblocks[3].has_potential_control_flow_jump()
158 changes: 158 additions & 0 deletions src/psyclone/tests/psyir/transformations/profile_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
# Modified by A. R. Porter, STFC Daresbury Lab
# Modified by I. Kavcic, Met Office
# Modified by S. Siso, STFC Daresbury Lab
# Modified by A. B. G. Chalk, STFC Daresbury Lab

''' Module containing tests for generating monitoring hooks'''

Expand Down Expand Up @@ -815,3 +816,160 @@ def test_auto_invoke_empty_schedule(capsys):
_, err = capsys.readouterr()
assert ("Not adding profiling to routine 'work1' because it does not "
"contain any statements." in err)


def test_profiling_exit_statement(fortran_reader):
''' Check the profiling transformation validation fails if there is an
EXIT block in the region.'''

code = """subroutine a()
integer :: i
do i = 1, 100
EXIT
end do
i = 1
end subroutine a
"""
psyir = fortran_reader.psyir_from_source(code)

ptrans = ProfileTrans()
with pytest.raises(TransformationError) as excinfo:
ptrans.validate(psyir.children[0].children[0])
assert ("Cannot apply the ProfileTrans to a code region containing a "
"potential control flow jump, as these could skip the end of "
"profiling caliper. Found:\n'! PSyclone CodeBlock "
"(unsupported code) reason:\n! - Unsupported statement: "
"Exit_Stmt\nEXIT\n'"
in str(excinfo.value))


def test_profiling_goto_statement(fortran_reader):
''' Check the profiling transformation validation fails if there is an
GOTO block in the region.'''

code = """subroutine a()
integer :: i
integer :: a
do i = 1, 100
a = a + i
GOTO 123
end do
123 i = 1
end subroutine a
"""
psyir = fortran_reader.psyir_from_source(code)
ptrans = ProfileTrans()
with pytest.raises(TransformationError) as excinfo:
ptrans.validate(psyir.children[0].children[0])
assert ("Cannot apply the ProfileTrans to a code region containing a "
"potential control flow jump, as these could skip the end of "
"profiling caliper. Found:\n'\n! PSyclone CodeBlock "
"(unsupported code) reason:\n! - Unsupported statement: "
"Goto_Stmt\nGO TO 123\n'"
in str(excinfo.value))


def test_profiling_cycle_statement(fortran_reader):
''' Check the profiling transformation validation fails if there is an
CYCLE block in the region.'''

code = """subroutine a()
integer :: i
integer :: a
do i = 1, 100
a = a + i
CYCLE
end do
i = 1
end subroutine a
"""
psyir = fortran_reader.psyir_from_source(code)
ptrans = ProfileTrans()
with pytest.raises(TransformationError) as excinfo:
ptrans.validate(psyir.children[0].children[0])
assert ("Cannot apply the ProfileTrans to a code region containing a "
"potential control flow jump, as these could skip the end of "
"profiling caliper. Found:\n'\n! PSyclone CodeBlock "
"(unsupported code) reason:\n! - Unsupported statement: "
"Cycle_Stmt\nCYCLE\n'"
in str(excinfo.value))


def test_profiling_labelled_statement(fortran_reader):
''' Check the profiling transformation validation fails if there is an
labelled statement in the region.'''

code = """subroutine a()
integer :: i
integer :: a
do i = 1, 100
123 a = a + 1
end do
i = 1
end subroutine a
"""
psyir = fortran_reader.psyir_from_source(code)
ptrans = ProfileTrans()
with pytest.raises(TransformationError) as excinfo:
ptrans.validate(psyir.children[0].children[0])
assert ("Transformation Error: Cannot apply the ProfileTrans to a code "
"region containing a potential control flow jump, as these could "
"skip the end of profiling caliper. Found:\n"
"'! PSyclone CodeBlock (unsupported code) reason:\n! - "
"Unsupported labelled statement\n123 a = a + 1\n"
in str(excinfo.value))

code = """subroutine a()
integer :: i
integer :: a
do i = 1, 100
123 do a= 1, 100
end do
end do
i = 1
end subroutine a
"""
psyir = fortran_reader.psyir_from_source(code)
ptrans = ProfileTrans()
with pytest.raises(TransformationError) as excinfo:
ptrans.validate(psyir.children[0].children[0])
assert ("Transformation Error: Cannot apply the ProfileTrans to a code "
"region containing a potential control flow jump, as these could "
"skip the end of profiling caliper. Found:\n"
"'! PSyclone CodeBlock (unsupported code) reason:\n! - "
"Unsupported labelled statement\n123 DO a = 1, 100\nEND DO\n"
in str(excinfo.value))


def test_profiling_force(fortran_reader):
''' Check the profiling transformation validation doesn't fail if we
enable the force option.'''

code = """subroutine a()
integer :: i
integer :: a
do i = 1, 100
123 a = a + 1
end do
end subroutine a
"""
psyir = fortran_reader.psyir_from_source(code)
ptrans = ProfileTrans()
ptrans.validate(psyir.children[0].children[0], {"force": True})


def test_profiling_full_routine(fortran_reader):
''' Check the profiling transformation validation doesn't fail if we
give the full routine as an input.'''

code = """subroutine a()
integer :: i
integer :: a
do i = 1, 100
123 a = a + 1
end do
end subroutine a
"""
psyir = fortran_reader.psyir_from_source(code)
ptrans = ProfileTrans()
ptrans.validate(psyir.children[0])
Loading