Skip to content

Commit ca44fe8

Browse files
committed
Add initial code for Fortran bindings
1 parent 6fafa49 commit ca44fe8

File tree

4 files changed

+193
-12
lines changed

4 files changed

+193
-12
lines changed

config/ompi_configure_options.m4

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,8 @@ AM_CONDITIONAL(OMPI_OMPIO_SUPPORT, test "$ompi_want_ompio" = "1")
248248
AM_PATH_PYTHON([3.6],,[:])
249249
abi_file="${srcdir}/ompi/mpi/c/ompi_send.c"
250250
AS_IF([! test -e "$abi_file" && test "$PYTHON" = ":"],
251-
[AC_MSG_ERROR([Open MPI requires Python >=3.6 for generating the C ABI files. Aborting])])
252-
AM_CONDITIONAL(OMPI_GENERATE_C_INTERFACE_FILES,[test "$PYTHON" != ":"])
251+
[AC_MSG_ERROR([Open MPI requires Python >=3.6 for generating the bindings. Aborting])])
252+
AM_CONDITIONAL(OMPI_GENERATE_BINDINGS,[test "$PYTHON" != ":"])
253253

254254
AC_MSG_CHECKING([if want to enable standard ABI library])
255255
AC_ARG_ENABLE([standard-abi],

ompi/mpi/c/Makefile.am

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ prototype_sources = \
7272

7373
# Include script and template files in case someone wants to update the
7474
# ABI files
75-
EXTRA_DIST = abi.py $(prototype_sources)
75+
EXTRA_DIST = generate_bindings.py $(prototype_sources)
7676

7777
# attr_fn.c contains attribute manipulation functions which do not
7878
# profiling implications, and so are always built.
@@ -595,16 +595,16 @@ libmpi_c_noprofile_la_CPPFLAGS = -DOMPI_BUILD_MPI_PROFILING=0
595595
nobase_include_HEADERS = standard_abi/mpi.h
596596

597597
# ABI generation rules
598-
if OMPI_GENERATE_C_INTERFACE_FILES
599-
ompi_%.c: %.c.in abi.py abi.h
600-
$(PYTHON) $(srcdir)/abi.py source ompi $< > $@
601-
standard_%.c: %.c.in abi.py abi.h
602-
$(PYTHON) $(srcdir)/abi.py source standard $< > $@
603-
abi.h: abi.py $(prototype_sources)
604-
$(PYTHON) $(srcdir)/abi.py header --srcdir=$(srcdir) $(prototype_sources) > $@
605-
standard_abi/mpi.h: abi.py $(prototype_sources)
598+
if OMPI_GENERATE_BINDINGS
599+
ompi_%.c: %.c.in generate_bindings.py abi.h
600+
$(PYTHON) $(srcdir)/generate_bindings.py source ompi $< > $@
601+
standard_%.c: %.c.in generate_bindings.py abi.h
602+
$(PYTHON) $(srcdir)/generate_bindings.py source standard $< > $@
603+
abi.h: generate_bindings.py $(prototype_sources)
604+
$(PYTHON) $(srcdir)/generate_bindings.py header --srcdir=$(srcdir) $(prototype_sources) > $@
605+
standard_abi/mpi.h: generate_bindings.py $(prototype_sources)
606606
mkdir -p standard_abi
607-
$(PYTHON) $(srcdir)/abi.py header --srcdir=$(srcdir) --external $(prototype_sources) > $@
607+
$(PYTHON) $(srcdir)/generate_bindings.py header --srcdir=$(srcdir) --external $(prototype_sources) > $@
608608

609609
distclean-local:
610610
-rm -rf ompi_*.c standard_*.c abi.h standard_abi/
File renamed without changes.

ompi/mpi/fortran/generate_bindings.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
from abc import ABC, abstractmethod
2+
import re
3+
4+
5+
class FortranType(ABC):
6+
7+
def __init__(self, name, **kwargs):
8+
self.name = name
9+
self.bigcount = False
10+
11+
TYPES = {}
12+
13+
@classmethod
14+
def add(cls, type_name):
15+
"""Decorator for adding types."""
16+
def wrapper(class_):
17+
cls.TYPES[type_name] = class_
18+
return class_
19+
return wrapper
20+
21+
@classmethod
22+
def get(cls, type_name):
23+
return cls.TYPES[type_name]
24+
25+
@abstractmethod
26+
def declare(self):
27+
"""Return a declaration for the type."""
28+
29+
def declare_fortran_interface(self):
30+
"""Return the C binding declaration as seen from Fortran."""
31+
return self.declare()
32+
33+
def argument(self):
34+
"""Return the value to pass as an argument."""
35+
return self.name
36+
37+
38+
@FortranType.add('BUFFER')
39+
class BufferType(FortranType):
40+
def declare(self):
41+
return f'OMPI_FORTRAN_IGNORE_TKR_TYPE, INTENT(IN) :: {self.name}'
42+
43+
44+
@FortranType.add('COUNT')
45+
class CountType(FortranType):
46+
def declare(self):
47+
if self.bigcount:
48+
return f'INTEGER(KIND=MPI_COUNT_KIND), INTENT(IN) :: {self.name}'
49+
else:
50+
return f'INTEGER, INTENT(IN) :: {self.name}'
51+
52+
53+
@FortranType.add('DATATYPE')
54+
class DatatypeType(FortranType):
55+
def declare(self):
56+
return f'TYPE(MPI_Datatype), INTENT(IN) :: {self.name}'
57+
58+
def declare_cbinding_fortran(self):
59+
return 'INTEGER, INTENT(IN) :: {self.name}'
60+
61+
def argument(self):
62+
return f'{self.name}%MPI_VAL'
63+
64+
65+
@FortranType.add('RANK')
66+
class RankType(FortranType):
67+
def declare(self):
68+
return f'INTEGER, INTENT(IN) :: {self.name}'
69+
70+
71+
@FortranType.add('TAG')
72+
class TagType(FortranType):
73+
def declare(self):
74+
return f'INTEGER, INTENT(IN) :: {self.name}'
75+
76+
77+
@FortranType.add('COMM')
78+
class CommType(FortranType):
79+
def declare(self):
80+
return f'TYPE(MPI_Comm), INTENT(IN) :: {self.name}'
81+
82+
def declare_cbinding_fortran(self):
83+
return f'INTEGER, INTENT(IN) :: {self.name}'
84+
85+
def argument(self):
86+
return f'{self.name}%MPI_VAL'
87+
88+
89+
PROTOTYPE_RE = re.compile(r'^\w+\((\s*\w+\s+\w+\s*,?)+\)$')
90+
91+
92+
class PrototypeParseError(Exception):
93+
"""Thrown when a parsing error is encountered."""
94+
95+
96+
def fortran_f08_name(base_name):
97+
"""Produce the final f08 name from base_name."""
98+
return f'MPI_{base_name.capitalize()}_f08'
99+
100+
101+
def c_func_name(base_name):
102+
"""Produce the final C func name from base_name."""
103+
return f'ompi_{base_name}_internal_f08'
104+
105+
106+
def print_header():
107+
"""Print the fortran f08 file header."""
108+
print('#include "ompi/mpi/fortran/configure-fortran-output.h"')
109+
print('#include "mpi-f08-rename.h"')
110+
111+
112+
class FortranBinding:
113+
114+
def __init__(self, fname):
115+
with open(fname) as fp:
116+
data = []
117+
for line in fp:
118+
data.append(line.strip())
119+
data = ' '.join(data)
120+
data = data.strip()
121+
if PROTOTYPE_RE.match(data) is None:
122+
raise PrototypeParseError('Invalid function prototype for Fortran interface')
123+
start = data.index('(')
124+
end = data.index(')')
125+
self.fn_name = data[:start].strip()
126+
parameters = data[start+1:end].split(',')
127+
self.parameters = []
128+
for param in parameters:
129+
param = param.strip()
130+
type_, name = param.split()
131+
type_ = FortranType.get(type_)
132+
indent = ' '
133+
self.parameters.append(type_(name))
134+
135+
def _param_list(self):
136+
return ','.join(type_.name for type_ in self.parameters)
137+
138+
def _print_fortran_interface(self):
139+
"""Output the C subroutine binding for the Fortran code."""
140+
name = c_func_name(self.fn_name)
141+
print('interface')
142+
print(f'subroutine {name}({self._param_list()},ierror) &')
143+
print(f' BIND(C, name="{name}")')
144+
print(' implicit none')
145+
for type_ in self.parameters:
146+
print(f' {type_.declare_fortran_interface()}')
147+
print(' INTEGER, INTENT(OUT) :: ierror')
148+
print(f'end subroutine {name}')
149+
print('end interface')
150+
151+
def print_fbinding(self):
152+
"""Output the main MPI Fortran subroutine."""
153+
print('! THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT BY HAND.')
154+
155+
print_header()
156+
157+
# Interface for call to C function
158+
self._print_fortran_interface()
159+
160+
sub_name = fortran_f08_name(self.fn_name)
161+
c_func = c_func_name(self.fn_name)
162+
print('subroutine', f'{sub_name}({self._param_list()},ierror)')
163+
# Parameters/dummy variable declarations
164+
types = []
165+
for type_ in self.parameters:
166+
print(f' {type_.declare()}')
167+
# Add the integer error manually
168+
print(' INTEGER, OPTIONAL, INTENT(OUT) :: ierror')
169+
# Temporaries
170+
print(' INTEGER :: c_ierror')
171+
172+
# Call into the C function
173+
args = ','.join(type_.argument() for type_ in self.parameters)
174+
print(f' call {c_func}({args},c_ierror)')
175+
# Convert error type
176+
print(' if (present(ierror)) ierror = c_ierror')
177+
178+
print(f'end subroutine {sub_name}')
179+
180+
181+
FortranBinding('use-mpi-f08/send.in').print_fbinding()

0 commit comments

Comments
 (0)