Skip to content

Commit

Permalink
Merge pull request #385 from ecmwf-ifs/nams-cpp-like-optional-args
Browse files Browse the repository at this point in the history
Transpilation: optional arguments
  • Loading branch information
reuterbal authored Oct 7, 2024
2 parents 3a0919d + 3e6e7b8 commit 6ecf132
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 22 deletions.
14 changes: 13 additions & 1 deletion loki/backend/cgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
PREC_UNARY, PREC_LOGICAL_OR, PREC_LOGICAL_AND, PREC_NONE, PREC_CALL
)

from loki.logging import warning
from loki.tools import as_tuple
from loki.ir import Import, Stringifier, FindNodes
from loki.expression import (
Expand Down Expand Up @@ -140,6 +141,10 @@ def map_c_reference(self, expr, enclosing_prec, *args, **kwargs):
def map_c_dereference(self, expr, enclosing_prec, *args, **kwargs):
return self.format(' (*%s)', self.rec(expr.expression, PREC_NONE, *args, **kwargs))

def map_inline_call(self, expr, enclosing_prec, *args, **kwargs):
if expr.function.name.lower() == 'present':
return self.format('true /*ATTENTION: present({%s})*/', expr.parameters[0].name)
return super().map_inline_call(expr, enclosing_prec, *args, **kwargs)

class CCodegen(Stringifier):
"""
Expand Down Expand Up @@ -192,6 +197,13 @@ def _subroutine_argument_pass_by(self, a):
return '*'
if a.type.pointer:
return '*'
if a.type.optional:
return '*'
return ''

def _subroutine_optional_args(self, a):
if a.type.optional:
warning(f'Argument "{a}" is optional! No support for optional arguments in {self.__class__.__name__}.')
return ''

def _subroutine_declaration(self, o, **kwargs):
Expand All @@ -203,7 +215,7 @@ def _subroutine_declaration(self, o, **kwargs):
# for a, p, k in zip(o.arguments, pass_by, var_keywords)]
arguments = [
(f'{self._subroutine_argument_keyword(a)}{self.visit(a.type, **kwargs)} '
f'{self._subroutine_argument_pass_by(a)}{a.name}')
f'{self._subroutine_argument_pass_by(a)}{a.name}{self._subroutine_optional_args(a)}')
for a in o.arguments
]
opt_header = kwargs.get('header', False)
Expand Down
12 changes: 10 additions & 2 deletions loki/backend/cppgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ class CppCodeMapper(CCodeMapper):
A :class:`StringifyMapper`-derived visitor for Pymbolic expression trees that converts an
expression to a string adhering to standardized C++.
"""
# pylint: disable=abstract-method, unused-argument, unnecessary-pass
pass
# pylint: disable=abstract-method, unused-argument

def map_inline_call(self, expr, enclosing_prec, *args, **kwargs):
if expr.function.name.lower() == 'present':
return self.format('%s', expr.parameters[0].name)
return super().map_inline_call(expr, enclosing_prec, *args, **kwargs)


class CppCodegen(CCodegen):
Expand Down Expand Up @@ -68,6 +72,10 @@ def _subroutine_footer(self, o, **kwargs):
footer += [self.format_line('\n} // extern')] if opt_extern else []
return footer

def _subroutine_optional_args(self, a):
if a.type.optional:
return ' = nullptr'
return ''

def cppgen(ir, **kwargs):
"""
Expand Down
27 changes: 21 additions & 6 deletions loki/build/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ class Compiler:

CC = None
CFLAGS = None
CPP = None
CPPFLAGS = None
F90 = None
F90FLAGS = None
FC = None
Expand All @@ -137,6 +139,8 @@ class Compiler:
def __init__(self):
self.cc = self.CC or 'gcc'
self.cflags = self.CFLAGS or ['-g', '-fPIC']
self.cpp = self.CPP or 'g++'
self.cppflags = self.CPPFLAGS or ['-g', '-fPIC']
self.f90 = self.F90 or 'gfortran'
self.f90flags = self.F90FLAGS or ['-g', '-fPIC']
self.fc = self.FC or 'gfortran'
Expand Down Expand Up @@ -164,13 +168,13 @@ def compile_args(self, source, target=None, include_dirs=None, mod_dir=None, mod
mode : str, optional
One of ``'f90'`` (free form), ``'f'`` (fixed form) or ``'c'``
"""
assert mode in ['f90', 'f', 'c']
assert mode in ['f90', 'f', 'c', 'cpp']
include_dirs = include_dirs or []
cc = {'f90': self.f90, 'f': self.fc, 'c': self.cc}[mode]
cc = {'f90': self.f90, 'f': self.fc, 'c': self.cc, 'cpp': self.cpp}[mode]
args = [cc, '-c']
args += {'f90': self.f90flags, 'f': self.fcflags, 'c': self.cflags}[mode]
args += {'f90': self.f90flags, 'f': self.fcflags, 'c': self.cflags, 'cpp': self.cppflags}[mode]
args += self._include_dir_args(include_dirs)
if mode != 'c':
if mode not in ['c', 'cpp']:
args += self._mod_dir_args(mod_dir)
args += [] if target is None else ['-o', str(target)]
args += [str(source)]
Expand All @@ -194,13 +198,15 @@ def _mod_dir_args(self, mod_dir):
return []
return [f'-J{mod_dir!s}']

def compile(self, source, target=None, include_dirs=None, use_c=False, cwd=None):
def compile(self, source, target=None, include_dirs=None, use_c=False, use_cpp=False, cwd=None):
"""
Execute a build command for a given source.
"""
kwargs = {'target': target, 'include_dirs': include_dirs}
if use_c:
kwargs['mode'] = 'c'
if use_cpp:
kwargs['mode'] = 'cpp'
args = self.compile_args(source, **kwargs)
execute(args, cwd=cwd)

Expand Down Expand Up @@ -282,6 +288,8 @@ class GNUCompiler(Compiler):

CC = 'gcc'
CFLAGS = ['-g', '-fPIC']
CPP = 'g++'
CPPFLAGS = ['-g', '-fPIC']
F90 = 'gfortran'
F90FLAGS = ['-g', '-fPIC']
FC = 'gfortran'
Expand All @@ -293,6 +301,7 @@ class GNUCompiler(Compiler):
F2PY_FCOMPILER_TYPE = 'gnu95'

CC_PATTERN = re.compile(r'(^|/|\\)gcc\b')
CPP_PATTERN = re.compile(r'(^|/|\\)g\+\+\b')
FC_PATTERN = re.compile(r'(^|/|\\)gfortran\b')


Expand All @@ -303,6 +312,8 @@ class NvidiaCompiler(Compiler):

CC = 'nvc'
CFLAGS = ['-g', '-fPIC']
CPP = 'nvc++'
CPPFLAGS = ['-g', '-fPIC']
F90 = 'nvfortran'
F90FLAGS = ['-g', '-fPIC']
FC = 'nvfortran'
Expand All @@ -314,6 +325,7 @@ class NvidiaCompiler(Compiler):
F2PY_FCOMPILER_TYPE = 'nv'

CC_PATTERN = re.compile(r'(^|/|\\)nvc\b')
CPP_PATTERN = re.compile(r'(^|/|\\)nvc\+\+\b')
FC_PATTERN = re.compile(r'(^|/|\\)(pgf9[05]|pgfortran|nvfortran)\b')

def _mod_dir_args(self, mod_dir):
Expand Down Expand Up @@ -358,7 +370,8 @@ def get_compiler_from_env(env=None):
var_pattern_map = {
'F90': 'FC_PATTERN',
'FC': 'FC_PATTERN',
'CC': 'CC_PATTERN'
'CC': 'CC_PATTERN',
'CPP': 'CPP_PATTERN'
}
for var, pattern in var_pattern_map.items():
if env.get(var):
Expand All @@ -377,6 +390,7 @@ def get_compiler_from_env(env=None):
# overwrite compiler executable and compiler flags with environment values
var_compiler_map = {
'CC': 'cc',
'CPP': 'cpp',
'FC': 'fc',
'F90': 'f90',
'LD': 'ld',
Expand All @@ -388,6 +402,7 @@ def get_compiler_from_env(env=None):

var_flag_map = {
'CFLAGS': 'cflags',
'CPPFLAGS': 'cppflags',
'FCFLAGS': 'fcflags',
'LDFLAGS': 'ldflags',
}
Expand Down
5 changes: 3 additions & 2 deletions loki/build/obj.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ class Obj:
A single source object representing a single C or Fortran source file.
"""

MODEMAP = {'.f90': 'f90', '.f': 'f', '.c': 'c', '.cc': 'c'}
MODEMAP = {'.f90': 'f90', '.f': 'f', '.c': 'c', '.cc': 'c', '.cpp': 'cpp',
'.CC': 'cpp', '.cxx': 'cpp'}

# Default source and header extension recognized
# TODO: Make configurable!
_ext = ['.f90', '.F90', '.f', '.F', '.c']
_ext = ['.f90', '.F90', '.f', '.F', '.c', '.cpp', '.CC', '.cc', '.cxx']

def __new__(cls, *args, name=None, **kwargs): # pylint: disable=unused-argument
# Name is either provided or inferred from source_path
Expand Down
29 changes: 18 additions & 11 deletions loki/transformations/transpile/fortran_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pathlib import Path
from collections import OrderedDict

from loki.backend import cgen, fgen, cudagen
from loki.backend import cgen, fgen, cudagen, cppgen
from loki.batch import Transformation
from loki.expression import (
symbols as sym, Variable, InlineCall, RangeIndex, Scalar, Array,
Expand Down Expand Up @@ -121,20 +121,26 @@ def __init__(self, inline_elementals=True, use_c_ptr=False, path=None, language=
self.use_c_ptr = use_c_ptr
self.path = Path(path) if path is not None else None
self.language = language.lower()
assert self.language in ['c', 'cuda'] # , 'hip']
self._supported_languages = ['c', 'cpp', 'cuda']

if self.language == 'c':
self.codegen = cgen
elif self.language == 'cpp':
self.codegen = cppgen
elif self.language == 'cuda':
self.codegen = cudagen
# elif self.language == 'hip':
# self.langgen = hipgen
else:
assert False
raise ValueError(f'language "{self.language}" is not supported!'
f' (supported languages: "{self._supported_languages}")')

# Maps from original type name to ISO-C and C-struct types
self.c_structs = OrderedDict()

def file_suffix(self):
if self.language == 'cpp':
return '.cpp'
return '.c'

def transform_module(self, module, **kwargs):
if self.path is None:
path = Path(kwargs.get('path'))
Expand Down Expand Up @@ -188,7 +194,7 @@ def transform_subroutine(self, routine, **kwargs):

if role == 'kernel':
# Generate Fortran wrapper module
bind_name = None if self.language == 'c' else f'{routine.name.lower()}_c_launch'
bind_name = None if self.language in ['c', 'cpp'] else f'{routine.name.lower()}_c_launch'
wrapper = self.generate_iso_c_wrapper_routine(routine, self.c_structs, bind_name=bind_name)
contains = Section(body=(Intrinsic('CONTAINS'), wrapper))
self.wrapperpath = (path/wrapper.name.lower()).with_suffix('.F90')
Expand All @@ -197,7 +203,7 @@ def transform_subroutine(self, routine, **kwargs):

# Generate C source file from Loki IR
c_kernel = self.generate_c_kernel(routine, targets=targets)
self.c_path = (path/c_kernel.name.lower()).with_suffix('.c')
self.c_path = (path/c_kernel.name.lower()).with_suffix(self.file_suffix())
Sourcefile.to_file(source=fgen(module), path=self.wrapperpath)

# Generate C source file from Loki IR
Expand All @@ -219,8 +225,9 @@ def transform_subroutine(self, routine, **kwargs):

if depth > 1:
c_kernel.spec.prepend(Import(module=f'{c_kernel.name.lower()}.h', c_import=True))
self.c_path = (path/c_kernel.name.lower()).with_suffix('.c')
Sourcefile.to_file(source=self.codegen(c_kernel), path=self.c_path)
self.c_path = (path/c_kernel.name.lower()).with_suffix(self.file_suffix())
Sourcefile.to_file(source=self.codegen(c_kernel, extern=self.language=='cpp'),
path=self.c_path)
header_path = (path/c_kernel.name.lower()).with_suffix('.h')
Sourcefile.to_file(source=self.codegen(c_kernel, header=True), path=header_path)

Expand Down Expand Up @@ -442,7 +449,7 @@ def generate_iso_c_interface(self, routine, bind_name, c_structs, scope):
else:
# Only scalar, intent(in) arguments are pass by value
# Pass by reference for array types
value = isinstance(arg, Scalar) and arg.type.intent.lower() == 'in'
value = isinstance(arg, Scalar) and arg.type.intent.lower() == 'in' and not arg.type.optional
kind = self.iso_c_intrinsic_kind(arg.type, intf_routine, is_array=isinstance(arg, Array))
if self.use_c_ptr:
if isinstance(arg, Array):
Expand Down Expand Up @@ -525,7 +532,7 @@ def apply_de_reference(routine):
"""
to_be_dereferenced = []
for arg in routine.arguments:
if not(arg.type.intent.lower() == 'in' and isinstance(arg, Scalar)):
if not(arg.type.intent.lower() == 'in' and isinstance(arg, Scalar)) or arg.type.optional:
to_be_dereferenced.append(arg.name.lower())

routine.body = DeReferenceTrafo(to_be_dereferenced).visit(routine.body)
Expand Down
Loading

0 comments on commit 6ecf132

Please sign in to comment.