Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

297 omp sentinels #326

Merged
merged 5 commits into from
Jul 18, 2024
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
28 changes: 26 additions & 2 deletions source/fab/parse/fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pathlib import Path
from typing import Union, Optional, Iterable, Dict, Any, Set

from fparser.common.readfortran import FortranStringReader # type: ignore
from fparser.two.Fortran2003 import ( # type: ignore
Entity_Decl_List, Use_Stmt, Module_Stmt, Program_Stmt, Subroutine_Stmt, Function_Stmt, Language_Binding_Spec,
Char_Literal_Constant, Interface_Block, Name, Comment, Module, Call_Stmt, Derived_Type_Def, Derived_Type_Stmt,
Expand Down Expand Up @@ -284,15 +285,38 @@ def _process_comment(self, analysed_file, obj):
# TODO: error handling in case we catch a genuine comment
# TODO: separate this project-specific code from the generic f analyser?
depends_str = "DEPENDS ON:"
if depends_str in obj.items[0]:
comment = obj.items[0].strip()
if depends_str in comment:
self.depends_on_comment_found = True
dep = obj.items[0].split(depends_str)[-1].strip()
dep = comment.split(depends_str)[-1].strip()
# with .o means a c file
if dep.endswith(".o"):
analysed_file.mo_commented_file_deps.add(dep.replace(".o", ".c"))
# without .o means a fortran symbol
else:
analysed_file.add_symbol_dep(dep)
if comment[:2] == "!$":
# Check if it is a use statement with an OpenMP sentinel:
# Use fparser's string reader to discard potential comment
# TODO #13: once fparser supports reading the sentinels,
# this can be removed.
MatthewHambley marked this conversation as resolved.
Show resolved Hide resolved
reader = FortranStringReader(comment[2:])
line = reader.next()
try:
# match returns a 5-tuple, the third one being the module name
module_name = Use_Stmt.match(line.strline)[2]
module_name = module_name.string
except Exception:
# Not a use statement in a sentinel, ignore:
return

# Register the module name
if module_name in self.ignore_mod_deps:
logger.debug(f"ignoring use of {module_name}")
return
if module_name.lower() not in self._intrinsic_modules:
# found a dependency on fortran
analysed_file.add_module_dep(module_name)

def _process_subroutine_or_function(self, analysed_file, fpath, obj):
# binding?
Expand Down
9 changes: 9 additions & 0 deletions tests/unit_tests/parse/fortran/test_fortran_analyser.f90
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ SUBROUTINE internal_sub
RETURN
END SUBROUTINE internal_sub

SUBROUTINE openmp_sentinel
!$ USE compute_chunk_size_mod, ONLY: compute_chunk_size ! Note OpenMP sentinel
!$ USE test that is not a sentinel with a use statement inside
!GCC$ unroll 6
!DIR$ assume (mod(p, 6) == 0)
!$omp do
!$acc parallel copyin (array, scalar).
END SUBROUTINE openmp_sentinel
MatthewHambley marked this conversation as resolved.
Show resolved Hide resolved

INTEGER FUNCTION internal_func()
internal_func = 456
END FUNCTION internal_func
Expand Down
53 changes: 35 additions & 18 deletions tests/unit_tests/parse/fortran/test_fortran_analyser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,35 @@
from fab.parse.fortran_common import iter_content
from fab.tools import ToolBox

'''Tests the Fortran analyser.
'''

# todo: test function binding


@pytest.fixture
def module_fpath():
def module_fpath() -> Path:
'''Simple fixture that sets the name of the module test file.'''
return Path(__file__).parent / "test_fortran_analyser.f90"


@pytest.fixture
def module_expected(module_fpath):
def module_expected(module_fpath) -> AnalysedFortran:
'''Returns the expected AnalysedFortran instance for the Fortran
test module.'''
return AnalysedFortran(
fpath=module_fpath,
file_hash=4039845747,
file_hash=1757501304,
module_defs={'foo_mod'},
symbol_defs={'external_sub', 'external_func', 'foo_mod'},
module_deps={'bar_mod'},
symbol_deps={'monty_func', 'bar_mod'},
module_deps={'bar_mod', 'compute_chunk_size_mod'},
symbol_deps={'monty_func', 'bar_mod', 'compute_chunk_size_mod'},
file_deps=set(),
mo_commented_file_deps={'some_file.c'},
)


class Test_Analyser(object):
class TestAnalyser:

@pytest.fixture
def fortran_analyser(self, tmp_path):
Expand All @@ -53,29 +58,37 @@ def fortran_analyser(self, tmp_path):
def test_empty_file(self, fortran_analyser):
# make sure we get back an EmptySourceFile
with mock.patch('fab.parse.AnalysedFile.save'):
analysis, artefact = fortran_analyser.run(fpath=Path(Path(__file__).parent / "empty.f90"))
assert type(analysis) is EmptySourceFile
analysis, artefact = fortran_analyser.run(
fpath=Path(Path(__file__).parent / "empty.f90"))
assert isinstance(analysis, EmptySourceFile)
assert artefact is None

def test_module_file(self, fortran_analyser, module_fpath, module_expected):
def test_module_file(self, fortran_analyser, module_fpath,
module_expected):
with mock.patch('fab.parse.AnalysedFile.save'):
analysis, artefact = fortran_analyser.run(fpath=module_fpath)
assert analysis == module_expected
assert artefact == fortran_analyser._config.prebuild_folder / f'test_fortran_analyser.{analysis.file_hash}.an'
assert artefact == (fortran_analyser._config.prebuild_folder /
f'test_fortran_analyser.{analysis.file_hash}.an')

def test_program_file(self, fortran_analyser, module_fpath, module_expected):
def test_program_file(self, fortran_analyser, module_fpath,
module_expected):
# same as test_module_file() but replacing MODULE with PROGRAM
with NamedTemporaryFile(mode='w+t', suffix='.f90') as tmp_file:
tmp_file.write(module_fpath.open().read().replace("MODULE", "PROGRAM"))
tmp_file.write(module_fpath.open().read().replace("MODULE",
"PROGRAM"))
tmp_file.flush()
with mock.patch('fab.parse.AnalysedFile.save'):
analysis, artefact = fortran_analyser.run(fpath=Path(tmp_file.name))
analysis, artefact = fortran_analyser.run(
fpath=Path(tmp_file.name))

module_expected.fpath = Path(tmp_file.name)
module_expected._file_hash = 768896775
module_expected._file_hash = 3388519280
module_expected.program_defs = {'foo_mod'}
module_expected.module_defs = set()
module_expected.symbol_defs.update({'internal_sub', 'internal_func'})
module_expected.symbol_defs.update({'internal_func',
'internal_sub',
'openmp_sentinel'})

assert analysis == module_expected
assert artefact == fortran_analyser._config.prebuild_folder \
Expand All @@ -84,11 +97,13 @@ def test_program_file(self, fortran_analyser, module_fpath, module_expected):

# todo: test more methods!

class Test_process_variable_binding(object):
class TestProcessVariableBinding:
'''This test class tests the variable binding.'''

# todo: define and depend, with and without bind name

def test_define_without_bind_name(self, tmp_path):
'''Test usage of bind'''
fpath = tmp_path / 'temp.f90'

open(fpath, 'wt').write("""
Expand All @@ -112,13 +127,15 @@ def test_define_without_bind_name(self, tmp_path):
tree = f2008_parser(reader)

# find the tree node representing the variable binding
var_decl = next(obj for obj in iter_content(tree) if isinstance(obj, Type_Declaration_Stmt))
var_decl = next(obj for obj in iter_content(tree)
if isinstance(obj, Type_Declaration_Stmt))

# run our handler
fpath = Path('foo')
analysed_file = AnalysedFortran(fpath=fpath, file_hash=0)
analyser = FortranAnalyser()
analyser._process_variable_binding(analysed_file=analysed_file, obj=var_decl)
analyser._process_variable_binding(analysed_file=analysed_file,
obj=var_decl)

assert analysed_file.symbol_defs == {'helloworld', }

Expand Down