Skip to content

Commit

Permalink
Merge branch 'main' into nams_f2c_case_sensitive
Browse files Browse the repository at this point in the history
  • Loading branch information
reuterbal authored Apr 11, 2024
2 parents 4c768f9 + a594b44 commit 140368d
Show file tree
Hide file tree
Showing 43 changed files with 2,852 additions and 1,144 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ jobs:
with:
flags: loki
files: ./coverage.xml
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

- name: Run transformations tests
run: |
Expand All @@ -74,6 +76,8 @@ jobs:
with:
flags: transformations
files: ./coverage.xml
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

- name: Run lint_rules tests
run: |
Expand All @@ -85,3 +89,5 @@ jobs:
with:
flags: lint_rules
files: ./coverage.xml
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
9 changes: 7 additions & 2 deletions cmake/loki_transform.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,10 @@ function( loki_transform_target )

set( options
NO_PLAN_SOURCEDIR COPY_UNMODIFIED CPP CPP_PLAN INLINE_MEMBERS
RESOLVE_SEQUENCE_ASSOCIATION DERIVE_ARGUMENT_ARRAY_SHAPE TRIM_VECTOR_SECTIONS
RESOLVE_SEQUENCE_ASSOCIATION DERIVE_ARGUMENT_ARRAY_SHAPE TRIM_VECTOR_SECTIONS GLOBAL_VAR_OFFLOAD
)
set( single_value_args TARGET COMMAND MODE DIRECTIVE FRONTEND CONFIG PLAN )
set( multi_value_args SOURCES HEADERS DEFINITIONS )
set( multi_value_args SOURCES HEADERS DEFINITIONS INCLUDES )

cmake_parse_arguments( _PAR_T "${options}" "${single_value_args}" "${multi_value_args}" ${ARGN} )

Expand Down Expand Up @@ -307,6 +307,10 @@ function( loki_transform_target )
list( APPEND _TRANSFORM_OPTIONS TRIM_VECTOR_SECTIONS )
endif()

if( _PAR_T_GLOBAL_VAR_OFFLOAD )
list( APPEND _TRANSFORM_OPTIONS GLOBAL_VAR_OFFLOAD )
endif()

loki_transform(
COMMAND ${_PAR_T_COMMAND}
OUTPUT ${LOKI_SOURCES_TO_APPEND}
Expand All @@ -318,6 +322,7 @@ function( loki_transform_target )
SOURCES ${_PAR_T_SOURCES}
HEADERS ${_PAR_T_HEADERS}
DEFINITIONS ${_PAR_T_DEFINITIONS}
INCLUDES ${_PAR_T_INCLUDES}
DEPENDS ${LOKI_SOURCES_TO_TRANSFORM} ${_PAR_T_HEADER} ${_PAR_T_CONFIG}
${_TRANSFORM_OPTIONS}
)
Expand Down
55 changes: 51 additions & 4 deletions loki/backend/cgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
PREC_UNARY, PREC_LOGICAL_OR, PREC_LOGICAL_AND, PREC_NONE, PREC_CALL
)

from loki.tools import as_tuple
from loki.ir import Import, Stringifier, FindNodes
from loki.expression import LokiStringifyMapper, Array, symbolic_op, Literal
from loki.expression import (
LokiStringifyMapper, Array, symbolic_op, Literal,
symbols as sym
)
from loki.types import BasicType, SymbolAttributes, DerivedType

__all__ = ['cgen', 'CCodegen', 'CCodeMapper']


Expand Down Expand Up @@ -171,7 +174,14 @@ def visit_Subroutine(self, o, **kwargs):
aptr += ['']
arguments = [f'{self.visit(a.type, **kwargs)} {p}{a.name}'
for a, p in zip(o.arguments, aptr)]
header += [self.format_line('int ', o.name, '(', self.join_items(arguments), ') {')]

# check whether to return something and define function return type accordingly
if o.is_function:
return_type = c_intrinsic_type(o.return_type)
else:
return_type = 'void'

header += [self.format_line(f'{return_type} ', o.name, '(', self.join_items(arguments), ') {')]

self.depth += 1

Expand All @@ -180,7 +190,10 @@ def visit_Subroutine(self, o, **kwargs):

# Fill the body
body += [self.visit(o.body, **kwargs)]
body += [self.format_line('return 0;')]

# if something to be returned, add 'return <var>' statement
if o.result_name is not None:
body += [self.format_line(f'return {o.result_name.lower()};')]

# Close everything off
self.depth -= 1
Expand Down Expand Up @@ -364,6 +377,40 @@ def visit_TypeDef(self, o, **kwargs):
self.depth -= 1
return self.join_lines(header, decls, footer)

def visit_MultiConditional(self, o, **kwargs):
"""
Format as
switch case (<expr>) {
case <value>:
...body...
[case <value>:]
[...body...]
[default:]
[...body...]
}
"""
header = self.format_line('switch (', self.visit(o.expr, **kwargs), ') {')
cases = []
end_cases = []
for value in o.values:
if any(isinstance(val, sym.RangeIndex) for val in value):
# TODO: in Fortran a case can be a range, which is not straight-forward
# to translate/transfer to C
#  https://j3-fortran.org/doc/year/10/10-007.pdf#page=200
raise NotImplementedError
case = self.visit_all(as_tuple(value), **kwargs)
cases.append(self.format_line('case ', self.join_items(case), ':'))
end_cases.append(self.format_line('break;'))
if o.else_body:
cases.append(self.format_line('default: '))
end_cases.append(self.format_line('break;'))
footer = self.format_line('}')
self.depth += 1
bodies = self.visit_all(*o.bodies, o.else_body, **kwargs)
self.depth -= 1
branches = [item for branch in zip(cases, bodies, end_cases) for item in branch]
return self.join_lines(header, *branches, footer)


def cgen(ir):
"""
Expand Down
3 changes: 2 additions & 1 deletion loki/backend/fgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ def visit_Subroutine(self, o, **kwargs):
if o.prefix:
prefix += ' '
arguments = self.join_items(o.argnames)
result = f' RESULT({o.result_name})' if o.result_name else ''
result = f' RESULT({o.result_name})' if o.result_name\
and o.result_name.lower() != o.name.lower() else ''
if isinstance(o.bind, str):
bind_c = f' BIND(c, name="{o.bind}")'
elif isinstance(o.bind, StringLiteral):
Expand Down
66 changes: 62 additions & 4 deletions loki/batch/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
from loki.logging import error, warning


__all__ = ['SchedulerConfig', 'TransformationConfig', 'ItemConfig']
__all__ = [
'SchedulerConfig', 'TransformationConfig', 'PipelineConfig',
'ItemConfig'
]


class SchedulerConfig:
Expand Down Expand Up @@ -52,7 +55,8 @@ class SchedulerConfig:

def __init__(
self, default, routines, disable=None, dimensions=None,
transformation_configs=None, enable_imports=False, frontend_args=None
transformation_configs=None, pipeline_configs=None,
enable_imports=False, frontend_args=None
):
self.default = default
self.disable = as_tuple(disable)
Expand All @@ -61,6 +65,7 @@ def __init__(

self.routines = CaseInsensitiveDict(routines)
self.transformation_configs = transformation_configs
self.pipeline_configs = pipeline_configs
self.frontend_args = frontend_args

# Resolve the dimensions for trafo configurations
Expand All @@ -72,6 +77,12 @@ def __init__(
name: config.instantiate() for name, config in self.transformation_configs.items()
}

# Instantiate Pipeline objects
self.pipelines = {
name: config.instantiate(transformation_map=self.transformations)
for name, config in self.pipeline_configs.items()
}

@classmethod
def from_dict(cls, config):
default = config.get('default', {})
Expand All @@ -91,10 +102,16 @@ def from_dict(cls, config):
}
frontend_args = config.get('frontend_args', {})

pipeline_configs = config.get('pipelines', {})
pipeline_configs = {
name: PipelineConfig(name=name, **cfg)
for name, cfg in pipeline_configs.items()
}

return cls(
default=default, routines=routines, disable=disable, dimensions=dimensions,
transformation_configs=transformation_configs, frontend_args=frontend_args,
enable_imports=enable_imports
transformation_configs=transformation_configs, pipeline_configs=pipeline_configs,
frontend_args=frontend_args, enable_imports=enable_imports
)

@classmethod
Expand Down Expand Up @@ -304,6 +321,47 @@ def instantiate(self):
return transformation


class PipelineConfig:
"""
Configuration object for custom :any:`Pipeline` instances that can
be used to create pipelines from other transformations stored in
the config.
Parameters
----------
name : str
Name of the transformation object
transformations : list of str
List of transformation names for which to look when
instnatiating thie pipeline.
"""


def __init__(self, name, transformations=None):
self.name = name
self.transformations = transformations or []

def instantiate(self, transformation_map=None):
"""
Creates a custom :any:`Pipeline` object from instantiated
:any:`Transformation` or :any:`Pipeline` objects in the given map.
"""
from loki.transform import Pipeline # pylint: disable=import-outside-toplevel,cyclic-import

# Create an empty pipeline and add from the map
pipeline = Pipeline(classes=())
for name in self.transformations:
if name not in transformation_map:
error(f'[Loki::Pipeline] Failed to find {name} in transformation config!')
raise RuntimeError(f'[Loki::Pipeline] Transformation {name} not found!')

# Use native notation to append transformation/pipeline,
# so that we may use them interchangably in config
pipeline += transformation_map[name]

return pipeline


class ItemConfig:
"""
:any:`Item`-specific configuration settings.
Expand Down
47 changes: 46 additions & 1 deletion loki/batch/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
)
from loki.frontend import FP, REGEX, RegexParserClass
from loki.tools import as_tuple, CaseInsensitiveDict, flatten
from loki.logging import info, perf, warning, debug
from loki.logging import info, perf, warning, debug, error


__all__ = ['Scheduler']
Expand Down Expand Up @@ -377,6 +377,46 @@ def rekey_item_cache(self):
)

def process(self, transformation):
"""
Process all :attr:`items` in the scheduler's graph with either
a :any:`Pipeline` or a single :any:`Transformation`.
A single :any:`Transformation` pass invokes
:meth:`process_transformation` individually, while a
:any:`Pipeline` will apply each contained transformation in
turn over the full dependency graph of the scheduler.
Parameters
----------
transformation : :any:`Transformation` or :any:`Pipeline`
The transformation or transformation pipeline to apply
"""
from loki.transform import Transformation, Pipeline # pylint: disable=import-outside-toplevel

if isinstance(transformation, Transformation):
self.process_transformation(transformation=transformation)

elif isinstance(transformation, Pipeline):
self.process_pipeline(pipeline=transformation)

else:
error('[Loki::Scheduler] Batch processing requires Transformation or Pipeline object')
raise RuntimeError('[Loki] Could not batch process {transformation_or_pipeline}')

def process_pipeline(self, pipeline):
"""
Process a given :any:`Pipeline` by applying its assocaited
transformations in turn.
Parameters
----------
transformation : :any:`Pipeline`
The transformation pipeline to apply
"""
for transformation in pipeline.transformations:
self.process_transformation(transformation)

def process_transformation(self, transformation):
"""
Process all :attr:`items` in the scheduler's graph
Expand All @@ -396,6 +436,11 @@ def process(self, transformation):
to ``True``. This uses the :attr:`filegraph` to process the dependency tree.
If combined with a :any:`Transformation.item_filter`, only source files with
at least one object corresponding to an item of that type are processed.
Parameters
----------
transformation : :any:`Transformation`
The transformation to apply over the dependency tree
"""
def _get_definition_items(_item, sgraph_items):
# For backward-compatibility with the DependencyTransform and LinterTransformation
Expand Down
8 changes: 8 additions & 0 deletions loki/dimension.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ def __init__(self, name=None, index=None, bounds=None, size=None, aliases=None):
self._size = size
self._aliases = as_tuple(aliases)

def __repr__(self):
""" Pretty-print dimension details """
name = f'<{self.name}>' if self.name else ''
index = str(self.index) or ''
size = str(self.size) or ''
bounds = ','.join(str(b) for b in self.bounds) if self.bounds else ''
return f'Dimension{name}[{index},{size},({bounds})]'

@property
def variables(self):
return (self.index, self.size) + self.bounds
Expand Down
12 changes: 12 additions & 0 deletions loki/expression/expr_visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ def visit_TypeDef(self, o, **kwargs):
"""
return self._return(o, ())

def visit_VariableDeclaration(self, o, **kwargs):
expressions = as_tuple(super().visit(o.children, **kwargs))
for v in o.symbols:
if v.type.initial is not None:
expressions += as_tuple(self.retrieve(v.type.initial))
return self._return(o, expressions)


class FindExpressions(ExpressionFinder):
"""
Expand Down Expand Up @@ -339,6 +346,11 @@ def visit_Scope(self, o, **kwargs):
# entry in the scope's table
self._update_symbol_table_with_decls_and_imports(o)

# Attach parent scope if it is new before passing self down to children
parent_scope = kwargs.get('scope', o.parent)
if o.parent is not parent_scope and o is not parent_scope:
o._reset_parent(parent=parent_scope)

# Then recurse to all children
kwargs['scope'] = o
children = tuple(self.visit(i, **kwargs) for i in o.children)
Expand Down
8 changes: 4 additions & 4 deletions loki/frontend/regex.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,10 +386,10 @@ def __init__(self):
r'^module[ \t]+(?P<name>\w+)\b.*?$'
r'(?P<spec>.*?)'
r'(?P<contains>^contains\n(?:'
r'(?:[ \t\w()=]*?subroutine.*?^end[ \t]*subroutine\b(?:[ \t]\w+)?\n)|'
r'(?:[ \t\w()=]*?function.*?^end[ \t]*function\b(?:[ \t]\w+)?\n)|'
r'(?:[ \t\w()=]*?subroutine.*?^end[ \t]*subroutine\b(?:[ \t]*\w+)?\n)|'
r'(?:[ \t\w()=]*?function.*?^end[ \t]*function\b(?:[ \t]*\w+)?\n)|'
r'(?:^#\w+.*?\n)'
r')*)?'
r')*?)?'
r'^end[ \t]*module\b(?:[ \t](?P=name))?',
re.IGNORECASE | re.DOTALL | re.MULTILINE
)
Expand Down Expand Up @@ -473,7 +473,7 @@ def __init__(self):
r'(?:[ \t\w()=]*?subroutine.*?^end[ \t]*subroutine\b(?:[ \t]\w+)?\n)|'
r'(?:[ \t\w()=]*?function.*?^end[ \t]*function\b(?:[ \t]\w+)?\n)|'
r'(?:^#\w+.*?\n)'
r')*)?'
r')*?)?'
r'^end[ \t]*(?P=keyword)\b(?:[ \t](?P=name))?',
re.IGNORECASE | re.DOTALL | re.MULTILINE
)
Expand Down
Loading

0 comments on commit 140368d

Please sign in to comment.