diff --git a/.gitignore b/.gitignore index 65d4083ba4c..68dc10a55b5 100644 --- a/.gitignore +++ b/.gitignore @@ -531,9 +531,10 @@ docs/_static docs/_static/css/custom.css docs/_templates -# Common Python virtual environment directory names +# Common Python virtual environment and cache directory names venv py?? +__pycache__/ # Copies of PRRTE RST files (i.e., not source controlled in this tree) docs/prrte-rst-content @@ -544,10 +545,13 @@ docs/schizo-ompi-rst-content docs/html docs/man +# Generated binding scripts +ompi/mpi/bindings/ompi_bindings/compiler.py + # Generated C Bindings ompi/mpi/c/ompi_*.c # Generated Fortran Bindings ompi/mpi/fortran/use-mpi-f08/*_generated.F90 ompi/mpi/fortran/use-mpi-f08/base/*_generated.c -ompi/mpi/fortran/use-mpi-f08/generate_bindings.py +ompi/mpi/fortran/use-mpi-f08/mod/mpi-f08-interfaces-generated.h diff --git a/config/ompi_config_files.m4 b/config/ompi_config_files.m4 index b373cf83920..3ea394a1104 100644 --- a/config/ompi_config_files.m4 +++ b/config/ompi_config_files.m4 @@ -44,9 +44,9 @@ AC_DEFUN([OMPI_CONFIG_FILES],[ ompi/mpi/fortran/use-mpi-f08/bindings/Makefile ompi/mpi/fortran/use-mpi-f08/mod/Makefile ompi/mpi/fortran/use-mpi-f08/mod/mpi-f08-interfaces.h - ompi/mpi/fortran/use-mpi-f08/generate_bindings.py ompi/mpi/fortran/mpiext-use-mpi/Makefile ompi/mpi/fortran/mpiext-use-mpi-f08/Makefile + ompi/mpi/bindings/ompi_bindings/compiler.py ompi/mpi/tool/Makefile ompi/tools/ompi_info/Makefile diff --git a/config/ompi_fortran_check_ts.m4 b/config/ompi_fortran_check_ts.m4 new file mode 100644 index 00000000000..1cc9d07a827 --- /dev/null +++ b/config/ompi_fortran_check_ts.m4 @@ -0,0 +1,69 @@ +dnl -*- shell-script -*- +dnl +dnl Copyright (c) 2019 Research Organization for Information Science +dnl and Technology (RIST). All rights reserved. +dnl $COPYRIGHT$ +dnl +dnl Additional copyrights may follow +dnl +dnl $HEADER$ +dnl + +# Check whether or not the C compiler supports ISO_Fortran_binding.h +# Also check whether C and Fortran compiler interoperate. +# +# OMPI_FORTRAN_CHECK_TS([action if found], [action if not found]) +# ---------------------------------------------------- +AC_DEFUN([OMPI_FORTRAN_CHECK_TS],[ + AS_VAR_PUSHDEF([fortran_ts], [ompi_cv_fortran_have_ts]) + + AC_CHECK_HEADERS([ISO_Fortran_binding.h], + [AC_CACHE_CHECK([if Fortran and C compilers support ISO_Fortran_binding.h], fortran_ts, + [mkdir conftest.$$ + cd conftest.$$ + + # Try to compile the C bindings + cat > conftest_c.c << EOF +#include + +int is_contiguous_c(CFI_cdesc_t* x) { + return CFI_is_contiguous(x); +} +EOF + OPAL_LOG_COMMAND([$CC $CCFLAGS -c conftest_c.c], + [cat > conftest.f90 << EOF +module MOD_IS_CONTIGUOUS + +interface + +function is_contiguous(buf) BIND(C, name="is_contiguous_c") + implicit none + type(*), dimension(..) :: buf + integer :: is_contiguous +end function is_contiguous + +end interface + +end module + +program test_is_contiguous + use MOD_IS_CONTIGUOUS + implicit none + integer :: a0, a1(2), a2(2,2), a3(2,2,2) + write (*,*) is_contiguous(a0) + write (*,*) is_contiguous(a1) + write (*,*) is_contiguous(a2) + write (*,*) is_contiguous(a3) +end program +EOF + OPAL_LOG_COMMAND([$FC $FCFLAGS $FCFLAGS_f90 -o conftest conftest.f90 conftest_c.o $LDFLAGS $LIBS], + [AS_VAR_SET(fortran_ts, yes)], + [AS_VAR_SET(fortran_ts, no)])], + [AS_VAR_SET(fortran_ts, no)]) + cd .. + rm -rf conftest.$$])], + [AS_VAR_SET(fortran_ts, no)]) + + AS_VAR_IF(fortran_ts, [yes], [$1], [$2]) + AS_VAR_POPDEF([fortran_ts])dnl +]) diff --git a/config/ompi_setup_mpi_fortran.m4 b/config/ompi_setup_mpi_fortran.m4 index 0fe0b333ebf..6225bbe70e2 100644 --- a/config/ompi_setup_mpi_fortran.m4 +++ b/config/ompi_setup_mpi_fortran.m4 @@ -449,14 +449,27 @@ end program]])], # If we got all the stuff from above, then also look for the new # F08 syntax that we can use for the use_mpif08 module. - # We need to have ignore TKR functionality to build the mpi_f08 + OMPI_FORTRAN_HAVE_TS=0 + OMPI_MPI_SUBARRAYS_SUPPORTED=.false. + OMPI_MPI_ASYNC_PROTECTS_NONBLOCKING=.false. + AS_IF([test $OMPI_TRY_FORTRAN_BINDINGS -ge $OMPI_FORTRAN_USEMPIF08_BINDINGS], + [OMPI_FORTRAN_CHECK_TS([OMPI_FORTRAN_HAVE_TS=1])]) + + AC_SUBST(OMPI_MPI_SUBARRAYS_SUPPORTED) + AC_SUBST(OMPI_MPI_ASYNC_PROTECTS_NONBLOCKING) + + # We need to have ignore TKR or the ISO Fortran bindings functionality to build the mpi_f08 # module - AS_IF([test $OMPI_TRY_FORTRAN_BINDINGS -ge $OMPI_FORTRAN_USEMPIF08_BINDINGS && \ - test $OMPI_FORTRAN_HAVE_IGNORE_TKR -eq 1], - [OMPI_BUILD_FORTRAN_BINDINGS=$OMPI_FORTRAN_USEMPIF08_BINDINGS - OMPI_FORTRAN_F08_PREDECL=$OMPI_FORTRAN_IGNORE_TKR_PREDECL - OMPI_FORTRAN_F08_TYPE=$OMPI_FORTRAN_IGNORE_TKR_TYPE - ]) + AS_IF([test $OMPI_TRY_FORTRAN_BINDINGS -ge $OMPI_FORTRAN_USEMPIF08_BINDINGS], + [AS_IF([test $OMPI_FORTRAN_HAVE_IGNORE_TKR -eq 1], + [OMPI_BUILD_FORTRAN_BINDINGS=$OMPI_FORTRAN_USEMPIF08_BINDINGS + OMPI_FORTRAN_F08_PREDECL=$OMPI_FORTRAN_IGNORE_TKR_PREDECL + OMPI_FORTRAN_F08_TYPE=$OMPI_FORTRAN_IGNORE_TKR_TYPE + ]) + AS_IF([test $OMPI_FORTRAN_HAVE_TS -eq 1], + [OMPI_BUILD_FORTRAN_BINDINGS=$OMPI_FORTRAN_USEMPIF08_BINDINGS + OMPI_MPI_SUBARRAYS_SUPPORTED=.true. + OMPI_MPI_ASYNC_PROTECTS_NONBLOCKING=.true.])]) # The overall "_BIND_C" variable will be set to 1 if we have all # the necessary forms of BIND(C) @@ -590,8 +603,6 @@ end type test_mpi_handle], ]) OMPI_FORTRAN_NEED_WRAPPER_ROUTINES=1 - OMPI_FORTRAN_F08_PREDECL='!' - OMPI_FORTRAN_F08_TYPE=real OMPI_FORTRAN_HAVE_F08_ASSUMED_RANK=0 AS_IF([test $OMPI_TRY_FORTRAN_BINDINGS -ge $OMPI_FORTRAN_USEMPIF08_BINDINGS && \ test $OMPI_BUILD_FORTRAN_BINDINGS -ge $OMPI_FORTRAN_USEMPIF08_BINDINGS], @@ -599,8 +610,6 @@ end type test_mpi_handle], OMPI_FORTRAN_CHECK_F08_ASSUMED_RANK( [ # If we have assumed rank, we can build the use # mpi_f08 module "better" - OMPI_FORTRAN_F08_PREDECL='!' - OMPI_FORTRAN_F08_TYPE='type(*), dimension(..)' OMPI_FORTRAN_HAVE_F08_ASSUMED_RANK=1]) # Which mpi_f08 implementation are we using? @@ -630,6 +639,12 @@ end type test_mpi_handle], [OMPI_FORTRAN_ELEMENTAL_TYPE=])]) AC_SUBST(OMPI_FORTRAN_ELEMENTAL_TYPE) + OMPI_FORTRAN_HAVE_C_ISO_FORTRAN=0 + AS_IF([test $OMPI_TRY_FORTRAN_BINDINGS -ge $OMPI_FORTRAN_USEMPIF08_BINDINGS && \ + test $OMPI_BUILD_FORTRAN_BINDINGS -ge $OMPI_FORTRAN_USEMPIF08_BINDINGS], + [OMPI_FORTRAN_CHECK_TS([OMPI_FORTRAN_HAVE_TS=1], + [OMPI_FORTRAN_HAVE_TS=0])]) + # Note: the current implementation *only* has wrappers; # there is no optimized implementation for a "good" # compiler. I'm leaving the above logic in place for @@ -652,6 +667,8 @@ end type test_mpi_handle], AS_IF([test $OMPI_MIN_REQUIRED_FORTRAN_BINDINGS -gt $OMPI_BUILD_FORTRAN_BINDINGS], [AC_MSG_ERROR([Cannot build requested Fortran bindings, aborting])]) + dnl AC_CONFIG_FILES([ompi/mpi/fortran/use-mpi-f08/bindings/mpi-f-interfaces-bind.h]) + # ------------------- # mpif.h final setup # ------------------- @@ -792,10 +809,9 @@ end type test_mpi_handle], # This goes into mpifort-wrapper-data.txt AC_SUBST(OMPI_FORTRAN_USEMPIF08_LIB) - # These go into interfaces/mpi-f08-interfaces-[no]bind.h (and - # mpi-f*-interfaces*.h files) - AC_SUBST(OMPI_FORTRAN_F08_PREDECL) - AC_SUBST(OMPI_FORTRAN_F08_TYPE) + # These go into mod/mpi-f08-interfaces.h + AC_SUBST(OMPI_F08_IGNORE_TKR_PREDECL) + AC_SUBST(OMPI_F08_IGNORE_TKR_TYPE) AC_SUBST(OMPI_MPI_PREFIX) AC_SUBST(OMPI_MPI_BIND_PREFIX) @@ -877,6 +893,22 @@ end type test_mpi_handle], # For configure-fortran-output.h AC_SUBST(OMPI_FORTRAN_HAVE_BIND_C) + AM_CONDITIONAL(OMPI_FORTRAN_HAVE_TS, + [test $OMPI_FORTRAN_HAVE_TS -eq 1]) + AC_SUBST(OMPI_FORTRAN_HAVE_TS) + + AS_IF([test $OMPI_FORTRAN_HAVE_TS -eq 1], + [OMPI_F08_IGNORE_TKR_TYPE="type(*), dimension(..)" + OMPI_F08_IGNORE_TKR_PREDECL="! no attribute required for" + OMPI_F08_BINDINGS_EXTENSION="ts" + OMPI_F08_BINDINGS_TS_SUFFIX="ts"], + [OMPI_F08_IGNORE_TKR_TYPE=$OMPI_FORTRAN_IGNORE_TKR_TYPE + OMPI_F08_IGNORE_TKR_PREDECL=$OMPI_FORTRAN_IGNORE_TKR_PREDECL + OMPI_F08_BINDINGS_EXTENSION="f" + OMPI_F08_BINDINGS_TS_SUFFIX=""]) + AC_SUBST(OMPI_F08_BINDINGS_EXTENSION) + AC_SUBST(OMPI_F08_BINDINGS_TS_SUFFIX) + # Somewhat redundant because ompi/Makefile.am won't traverse into # ompi/mpi/fortran/use-mpi-f08 if it's not to be built, but we # might as well have ompi/mpi/fortran/use-mpi-f08/Makefile.am be diff --git a/ompi/include/mpi.h.in b/ompi/include/mpi.h.in index ec642fb38f8..257d9e672c6 100644 --- a/ompi/include/mpi.h.in +++ b/ompi/include/mpi.h.in @@ -1437,6 +1437,9 @@ OMPI_DECLSPEC int MPI_Alltoall_init(const void *sendbuf, int sendcount, MPI_Dat OMPI_DECLSPEC int MPI_Alltoallv(const void *sendbuf, const int sendcounts[], const int sdispls[], MPI_Datatype sendtype, void *recvbuf, const int recvcounts[], const int rdispls[], MPI_Datatype recvtype, MPI_Comm comm); +OMPI_DECLSPEC int MPI_Alltoallv_c(const void *sendbuf, const MPI_Count sendcounts[], const MPI_Aint sdispls[], MPI_Datatype sendtype, + void *recvbuf, const MPI_Count recvcounts[], const MPI_Aint rdispls[], MPI_Datatype recvtype, + MPI_Comm comm); OMPI_DECLSPEC int MPI_Ialltoallv(const void *sendbuf, const int sendcounts[], const int sdispls[], MPI_Datatype sendtype, void *recvbuf, const int recvcounts[], const int rdispls[], MPI_Datatype recvtype, MPI_Comm comm, MPI_Request *request); @@ -1446,6 +1449,9 @@ OMPI_DECLSPEC int MPI_Alltoallv_init(const void *sendbuf, const int sendcounts[ OMPI_DECLSPEC int MPI_Alltoallw(const void *sendbuf, const int sendcounts[], const int sdispls[], const MPI_Datatype sendtypes[], void *recvbuf, const int recvcounts[], const int rdispls[], const MPI_Datatype recvtypes[], MPI_Comm comm); +OMPI_DECLSPEC int MPI_Alltoallw_c(const void *sendbuf, const MPI_Count sendcounts[], const MPI_Aint sdispls[], const MPI_Datatype sendtypes[], + void *recvbuf, const MPI_Count recvcounts[], const MPI_Aint rdispls[], const MPI_Datatype recvtypes[], + MPI_Comm comm); OMPI_DECLSPEC int MPI_Ialltoallw(const void *sendbuf, const int sendcounts[], const int sdispls[], const MPI_Datatype sendtypes[], void *recvbuf, const int recvcounts[], const int rdispls[], const MPI_Datatype recvtypes[], MPI_Comm comm, MPI_Request *request); @@ -2209,6 +2215,9 @@ OMPI_DECLSPEC int PMPI_Alltoall_init(const void *sendbuf, int sendcount, MPI_Da OMPI_DECLSPEC int PMPI_Alltoallv(const void *sendbuf, const int sendcounts[], const int sdispls[], MPI_Datatype sendtype, void *recvbuf, const int recvcounts[], const int rdispls[], MPI_Datatype recvtype, MPI_Comm comm); +OMPI_DECLSPEC int PMPI_Alltoallv_c(const void *sendbuf, const MPI_Count sendcounts[], const MPI_Aint sdispls[], MPI_Datatype sendtype, + void *recvbuf, const MPI_Count recvcounts[], const MPI_Aint rdispls[], MPI_Datatype recvtype, + MPI_Comm comm); OMPI_DECLSPEC int PMPI_Ialltoallv(const void *sendbuf, const int sendcounts[], const int sdispls[], MPI_Datatype sendtype, void *recvbuf, const int recvcounts[], const int rdispls[], MPI_Datatype recvtype, MPI_Comm comm, MPI_Request *request); @@ -2218,6 +2227,9 @@ OMPI_DECLSPEC int PMPI_Alltoallv_init(const void *sendbuf, const int sendcounts OMPI_DECLSPEC int PMPI_Alltoallw(const void *sendbuf, const int sendcounts[], const int sdispls[], const MPI_Datatype sendtypes[], void *recvbuf, const int recvcounts[], const int rdispls[], const MPI_Datatype recvtypes[], MPI_Comm comm); +OMPI_DECLSPEC int PMPI_Alltoallw_c(const void *sendbuf, const MPI_Count sendcounts[], const MPI_Aint sdispls[], const MPI_Datatype sendtypes[], + void *recvbuf, const MPI_Count recvcounts[], const MPI_Aint rdispls[], const MPI_Datatype recvtypes[], + MPI_Comm comm); OMPI_DECLSPEC int PMPI_Ialltoallw(const void *sendbuf, const int sendcounts[], const int sdispls[], const MPI_Datatype sendtypes[], void *recvbuf, const int recvcounts[], const int rdispls[], const MPI_Datatype recvtypes[], MPI_Comm comm, MPI_Request *request); diff --git a/ompi/mpi/bindings/bindings.py b/ompi/mpi/bindings/bindings.py new file mode 100644 index 00000000000..f3182b76c8a --- /dev/null +++ b/ompi/mpi/bindings/bindings.py @@ -0,0 +1,68 @@ +# Copyright (c) 2024 Triad National Security, LLC. All rights +# reserved. +# +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +import argparse +import os +import sys + + +def handle_missing_command(args, out): + print('missing subcommand (one of {fortran,c} required)', file=sys.stderr) + sys.exit(1) + + +def main(): + parser = argparse.ArgumentParser(description='generate fortran binding files') + parser.add_argument('--builddir', required=True, help='absolute path to automake builddir (abs_top_builddir)') + parser.add_argument('--output', required=True, help='output file to use') + parser.add_argument('--srcdir', required=True, help='absolute path to automake srcdir (abs_top_srcdir)') + parser.set_defaults(handler=handle_missing_command) + subparsers = parser.add_subparsers() + + # Fortran set up code + parser_fortran = subparsers.add_parser('fortran', help='subcommand for generating Fortran code') + parser_fortran.add_argument('--template', required=True, help='template file to use') + subparsers_fortran = parser_fortran.add_subparsers() + # Handler for generating actual code + parser_code = subparsers_fortran.add_parser('code', help='generate binding code') + parser_code.add_argument('lang', choices=('fortran', 'c'), + help='generate dependent files in C or Fortran') + parser_code.set_defaults(handler=lambda args, out: fortran.generate_code(args, out)) + # Handler for generating the Fortran interface files + parser_interface = subparsers_fortran.add_parser('interface', + help='generate Fortran interface specifcations') + parser_interface.set_defaults(handler=lambda args, out: fortran.generate_interface(args, out)) + + # C set up code + parser_c = subparsers.add_parser('c', help='subcommand for generating C code') + subparsers_c = parser_c.add_subparsers() + parser_header = subparsers_c.add_parser('header') + parser_header.add_argument('file', nargs='+', help='list of template source files') + parser_header.add_argument('--external', action='store_true', help='generate external mpi.h header file') + parser_header.add_argument('--srcdir', help='source directory') + parser_header.set_defaults(handler=lambda args, out: c.generate_header(args, out)) + parser_gen = subparsers_c.add_parser('source') + # parser = argparse.ArgumentParser(description='C ABI binding generation code') + parser_gen.add_argument('type', choices=('ompi', 'standard'), + help='generate the OMPI ABI functions or the standard ABI functions') + parser_gen.add_argument('source_file', help='template file to use for C code generation') + parser_gen.set_defaults(handler=lambda args, out: c.generate_source(args, out)) + args = parser.parse_args() + + # Pull in both generated python files and src files on import + sys.path.insert(0, os.path.join(args.builddir, 'ompi/mpi/bindings')) + sys.path.insert(0, os.path.join(args.srcdir, 'ompi/mpi/bindings')) + from ompi_bindings import c, fortran + from ompi_bindings.util import OutputFile + + with open(args.output, 'w') as f: + args.handler(args, OutputFile(f)) + + +if __name__ == '__main__': + main() diff --git a/ompi/mpi/bindings/ompi_bindings/c.py b/ompi/mpi/bindings/ompi_bindings/c.py new file mode 100644 index 00000000000..b2a136f4a77 --- /dev/null +++ b/ompi/mpi/bindings/ompi_bindings/c.py @@ -0,0 +1,505 @@ +# Copyright (c) 2024 Triad National Security, LLC. All rights reserved. +# Copyright (c) 2023 Research Organization for Information Science +# and Technology (RIST). All rights reserved. +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADERS$ +# +# +"""MPI Standard ABI Generation. + +TEMPLATE SOURCE FILE ASSUMPTIONS: +* Only one function per file +* Nothing (other than blank lines) after closing '}' +* Function prototype is preceded by PROTOTYPE +* All types in the function prototype are converted to one-word capital types + as defined here (to be later converted to ompi or standard ABI types) +* Functions requiring a bigcount implementation should have type COUNT in + place of MPI_Count or int for each count parameter. Bigcount functions will + be generated automatically for any function that includes a COUNT type. +""" +from abc import ABC, abstractmethod +import argparse +import re +import sys +import os +from ompi_bindings import consts, util +from ompi_bindings.consts import ConvertFuncs, ConvertOMPIToStandard +from ompi_bindings.c_type import Type + + +class ABIHeaderBuilder: + """ABI header builder code.""" + + def __init__(self, prototypes, out, external=False): + self.out = out + self.external = external + + if external: + mangle_name = lambda name: name + else: + mangle_name = util.abi_internal_name + + # Build up the list of standard ABI signatures + signatures = [] + for prototype in prototypes: + base_name = util.mpi_fn_name_from_base_fn_name(prototype.name) + signatures.append(prototype.signature('standard', base_name, + mangle_name=mangle_name)) + # Profiling prototype + signatures.append(prototype.signature('standard', f'P{base_name}', + mangle_name=mangle_name)) + if prototype.need_bigcount: + signatures.append(prototype.signature('standard', f'{base_name}_c', + enable_count=True, + mangle_name=mangle_name)) + # Profiling prototype + signatures.append(prototype.signature('standard', f'P{base_name}_c', + enable_count=True, + mangle_name=mangle_name)) + self.signatures = signatures + + def mangle_name(self, extname): + """Mangle names, depending on whether building external or internal header.""" + if self.external: + return extname + return util.abi_internal_name(extname) + + def dump(self, *pargs, **kwargs): + self.out.dump(*pargs, **kwargs) + + def dump_lines(self, lines): + lines = util.indent_lines(lines, 4 * ' ', start=1) + for line in lines: + self.dump(line) + + def generate_error_convert_fn(self): + self.dump(f'{consts.INLINE_ATTRS} int {ConvertFuncs.ERROR_CLASS}(int error_class)') + self.dump('{') + lines = [] + lines.append('switch (error_class) {') + for error in consts.ERROR_CLASSES: + lines.append(f'case {self.mangle_name(error)}:') + lines.append(f'return {error};') + lines.append('default:') + lines.append('return error_class;') + lines.append('}') + self.dump_lines(lines) + self.dump('}') + + def generic_convert(self, fn_name, param_name, type_, value_names): + intern_type = self.mangle_name(type_) + self.dump(f'{consts.INLINE_ATTRS} {type_} {fn_name}({intern_type} {param_name})') + self.dump('{') + lines = [] + for i, value_name in enumerate(value_names): + intern_name = self.mangle_name(value_name) + if i == 0: + lines.append('if (%s == %s) {' % (intern_name, param_name)) + else: + lines.append('} else if (%s == %s) {' % (intern_name, param_name)) + lines.append(f'return {value_name};') + lines.append('}') + lines.append(f'return ({type_}) {param_name};') + self.dump_lines(lines) + self.dump('}') + + def generic_convert_reverse(self, fn_name, param_name, type_, value_names): + intern_type = self.mangle_name(type_) + self.dump(f'{consts.INLINE_ATTRS} {intern_type} {fn_name}({type_} {param_name})') + self.dump('{') + lines = [] + for i, value_name in enumerate(value_names): + intern_name = self.mangle_name(value_name) + if i == 0: + lines.append('if (%s == %s) {' % (value_name, param_name)) + else: + lines.append('} else if (%s == %s) {' % (value_name, param_name)) + lines.append(f'return {intern_name};') + lines.append('}') + lines.append(f'return ({intern_type}) {param_name};') + self.dump_lines(lines) + self.dump('}') + + def generate_comm_convert_fn(self): + self.generic_convert(ConvertFuncs.COMM, 'comm', 'MPI_Comm', consts.RESERVED_COMMUNICATORS) + + def generate_comm_convert_fn_intern_to_abi(self): + self.generic_convert_reverse(ConvertOMPIToStandard.COMM, 'comm', 'MPI_Comm', consts.RESERVED_COMMUNICATORS) + + def generate_info_convert_fn(self): + self.generic_convert(ConvertFuncs.INFO, 'info', 'MPI_Info', consts.RESERVED_INFOS) + + def generate_file_convert_fn_intern_to_abi(self): + self.generic_convert_reverse(ConvertFuncs.FILE, 'file', 'MPI_File', consts.RESERVED_FILES) + + def generate_datatype_convert_fn(self): + self.generic_convert(ConvertFuncs.DATATYPE, 'datatype', 'MPI_Datatype', consts.PREDEFINED_DATATYPES) + + def generate_op_convert_fn(self): + self.generic_convert(ConvertFuncs.OP, 'op', 'MPI_Op', consts.COLLECTIVE_OPERATIONS) + + def generate_win_convert_fn(self): + self.generic_convert(ConvertFuncs.WIN, 'win', 'MPI_Win', consts.RESERVED_WINDOWS) + + def generate_pointer_convert_fn(self, type_, fn_name, constants): + abi_type = self.mangle_name(type_) + self.dump(f'{consts.INLINE_ATTRS} void {fn_name}({abi_type} *ptr)') + self.dump('{') + lines = [] + for i, ompi_name in enumerate(constants): + abi_name = self.mangle_name(ompi_name) + if i == 0: + lines.append('if (%s == (%s) *ptr) {' % (ompi_name, type_)) + else: + lines.append('} else if (%s == (%s) *ptr) {' % (ompi_name, type_)) + lines.append(f'*ptr = {abi_name};') + lines.append('}') + self.dump_lines(lines) + self.dump('}') + + def generate_request_convert_fn(self): + self.generate_pointer_convert_fn('MPI_Request', ConvertFuncs.REQUEST, consts.RESERVED_REQUESTS) + + def generate_file_convert_fn(self): + self.generate_pointer_convert_fn('MPI_File', ConvertFuncs.FILE, consts.RESERVED_FILES) + + def generate_status_convert_fn(self): + type_ = 'MPI_Status' + abi_type = self.mangle_name(type_) + self.dump(f'{consts.INLINE_ATTRS} void {ConvertFuncs.STATUS}({abi_type} *out, {type_} *inp)') + self.dump('{') + self.dump(' out->MPI_SOURCE = inp->MPI_SOURCE;') + self.dump(' out->MPI_TAG = inp->MPI_TAG;') + self.dump(f' out->MPI_ERROR = {ConvertFuncs.ERROR_CLASS}(inp->MPI_ERROR);') + # Ignoring the private fields for now + self.dump('}') + + def define(self, type_, name, value): + self.dump(f'#define {name} OMPI_CAST_CONSTANT({type_}, {value})') + + def define_all(self, type_, constants): + for i, const in enumerate(constants): + self.define(self.mangle_name(type_), self.mangle_name(const), i + 1) + self.dump() + + def dump_header(self): + header_guard = '_ABI_INTERNAL_' + self.dump(f'#ifndef {header_guard}') + self.dump(f'#define {header_guard}') + + self.dump('#include "stddef.h"') + self.dump('#include "stdint.h"') + + self.dump(""" +#if defined(c_plusplus) || defined(__cplusplus) +extern "C" { +#endif +""") + + self.dump(""" +#if defined(c_plusplus) || defined(__cplusplus) +#define OMPI_CAST_CONSTANT(type, value) (static_cast (static_cast (value))) +#else +#define OMPI_CAST_CONSTANT(type, value) ((type) ((void *) value)) +#endif +""") + + for i, err in enumerate(consts.ERROR_CLASSES): + self.dump(f'#define {self.mangle_name(err)} {i + 1}') + self.dump() + + self.define_all('MPI_Datatype', consts.PREDEFINED_DATATYPES) + self.define_all('MPI_Op', COLLECTIVE_OPERATIONS) + self.define_all('MPI_Comm', consts.RESERVED_COMMUNICATORS) + self.define_all('MPI_Request', consts.RESERVED_REQUESTS) + self.define_all('MPI_Win', consts.RESERVED_WINDOWS) + self.define_all('MPI_Info', consts.RESERVED_INFOS) + self.define_all('MPI_File', consts.RESERVED_FILES) + + for name, value in consts.VARIOUS_CONSTANTS.items(): + self.dump(f'#define {self.mangle_name(name)} {value}') + self.dump() + + status_type = self.mangle_name('MPI_Status') + for i, name in enumerate(consts.IGNORED_STATUS_HANDLES): + self.define(f'{status_type} *', self.mangle_name(name), i + 1) + self.dump() + + for i, name in enumerate(consts.COMMUNICATOR_SPLIT_TYPES): + self.dump(f'#define {self.mangle_name(name)} {i}') + self.dump() + + for mpi_type, c_type in consts.C_OPAQUE_TYPES.items(): + self.dump(f'typedef {c_type} {self.mangle_name(mpi_type)};') + self.dump() + + for handle in consts.C_HANDLES: + prefix, suffix = handle.split('_') + name = f'{prefix}_ABI_{suffix}' + self.dump(f'typedef struct {self.mangle_name(name)} *{self.mangle_name(handle)};') + self.dump() + self.dump(""" +struct MPI_Status_ABI { + int MPI_SOURCE; + int MPI_TAG; + int MPI_ERROR; + int mpi_abi_private[5]; +};""") + self.dump(f'typedef struct MPI_Status_ABI {self.mangle_name("MPI_Status")};') + self.dump() + # Function signatures + for sig in self.signatures: + self.dump(f'{sig};') + self.dump('int MPI_Abi_details(int *buflen, char *details, MPI_Info *info);') + self.dump('int MPI_Abi_supported(int *flag);') + self.dump('int MPI_Abi_version(int *abi_major, int *abi_minor);') + if not self.external: + # Now generate the conversion code + self.generate_error_convert_fn() + self.generate_comm_convert_fn() + self.generate_comm_convert_fn_intern_to_abi() + self.generate_info_convert_fn() + self.generate_file_convert_fn() + self.generate_datatype_convert_fn() + self.generate_op_convert_fn() + self.generate_win_convert_fn() + self.generate_request_convert_fn() + self.generate_status_convert_fn() + + self.dump(""" +#if defined(c_plusplus) || defined(__cplusplus) +} +#endif +""") + self.dump(f'#endif /* {header_guard} */') + + +class Parameter: + + def __init__(self, text): + """Parse a parameter.""" + # parameter in the form "TYPE NAME" or "TYPE NAME:COUNT_VAR" + type_, namecount = text.split() + if ':' in namecount: + name, count_param = namecount.split(':') + else: + name, count_param = namecount, None + self.type_ = type_ + self.name = name + self.count_param = count_param + + def construct(self, abi_type, **kwargs): + """Construct the type parameter for the given ABI.""" + return Type.construct(abi_type, type_=self.type_, name=self.name, + count_param=self.count_param, **kwargs) + + +class ReturnType: + """Return type wrapper.""" + + def __init__(self, type_): + self.type_ = type_ + + def construct(self, abi_type, **kwargs): + """Construct the return type for the given ABI.""" + return Type.construct(abi_type, type_=self.type_, **kwargs) + + +class Prototype: + """MPI function prototype.""" + + def __init__(self, name, return_type, params): + self.name = name + self.return_type = return_type + self.params = params + + def signature(self, abi_type, fn_name, enable_count=False, **kwargs): + """Build a signature with the given name and if count is enabled.""" + params = ', '.join(param.construct(abi_type, **kwargs).parameter(enable_count=enable_count, **kwargs) + for param in self.params) + if not params: + params = 'void' + return_type_text = self.return_type.construct(abi_type, **kwargs).type_text(enable_count=enable_count) + return f'{return_type_text} {fn_name}({params})' + + @property + def need_bigcount(self): + """Check if a bigcount interface is required for a prototype.""" + return any('COUNT' in param.type_ for param in self.params) + + +def validate_body(body): + """Validate the body of a template.""" + # Just do a simple bracket balance test to determine the bounds of the + # function body. All lines after the function body should be blank. There + # are cases where this will break, such as if someone puts code all on one + # line. + bracket_balance = 0 + line_count = 0 + for line in body: + line = line.strip() + if bracket_balance == 0 and line_count > 0 and line: + raise util.BindingError('Extra code found in template; only one function body is allowed') + + update = line.count('{') - line.count('}') + bracket_balance += update + if bracket_balance != 0: + line_count += 1 + + if bracket_balance != 0: + raise util.BindingError('Mismatched brackets found in template') + + +class SourceTemplate: + """Source template for a single API function.""" + + def __init__(self, prototype, header, body): + self.prototype = prototype + self.header = header + self.body = body + + @staticmethod + def load(fname, prefix=None): + """Load a template file and return the SourceTemplate.""" + if prefix is not None: + fname = os.path.join(prefix, fname) + with open(fname) as fp: + header = [] + prototype = [] + body = [] + + for line in fp: + line = line.rstrip() + if prototype and line.startswith('PROTOTYPE'): + raise util.BindingError('more than one prototype found in template file') + elif ((prototype and not any(')' in s for s in prototype)) + or line.startswith('PROTOTYPE')): + prototype.append(line) + elif prototype: + # Validate bracket balance + body.append(line) + else: + header.append(line) + + if not prototype: + raise RuntimeError('missing prototype') + # Parse the prototype + prototype = ''.join(prototype) + prototype = prototype[len('PROTOTYPE'):] + i = prototype.index('(') + j = prototype.index(')') + return_type, name = prototype[:i].split() + return_type = ReturnType(return_type) + params = [param.strip() for param in prototype[i + 1:j].split(',') if param.strip()] + params = [Parameter(param) for param in params] + prototype = Prototype(name, return_type, params) + # Ensure the body contains only one function + validate_body(body) + return SourceTemplate(prototype, header, body) + + def print_header(self, out): + """Print the source header.""" + for line in self.header: + out.dump(line) + + def print_body(self, func_name, out): + """Print the body.""" + for line in self.body: + # FUNC_NAME is used for error messages + line = line.replace('FUNC_NAME', f'"{func_name}"') + out.dump(line) + + +def print_profiling_header(fn_name, out): + """Print the profiling header code.""" + out.dump('#if OMPI_BUILD_MPI_PROFILING') + out.dump('#if OPAL_HAVE_WEAK_SYMBOLS') + out.dump(f'#pragma weak {fn_name} = P{fn_name}') + out.dump('#endif') + out.dump(f'#define {fn_name} P{fn_name}') + out.dump('#endif') + + +def ompi_abi(base_name, template, out): + """Generate the OMPI ABI functions.""" + template.print_header(out) + print_profiling_header(base_name, out) + out.dump(template.prototype.signature('ompi', base_name)) + template.print_body(func_name=base_name, out=out) + # Check if we need to generate the bigcount interface + if template.prototype.need_bigcount: + base_name_c = f'{base_name}_c' + print_profiling_header(base_name_c, out) + out.dump(template.prototype.signature('ompi', base_name_c, enable_count=True)) + template.print_body(func_name=base_name_c, out=out) + + +ABI_INTERNAL_HEADER = 'ompi/mpi/c/abi.h' + + +def standard_abi(base_name, template, out): + """Generate the standard ABI functions.""" + template.print_header(out) + out.dump(f'#include "{ABI_INTERNAL_HEADER}"') + + # Static internal function (add a random component to avoid conflicts) + internal_name = f'ompi_abi_{template.prototype.name}' + internal_sig = template.prototype.signature('ompi', internal_name, + enable_count=True) + out.dump(consts.INLINE_ATTRS, internal_sig) + template.print_body(func_name=base_name, out=out) + + def generate_function(prototype, fn_name, internal_fn, enable_count=False): + """Generate a function for the standard ABI.""" + print_profiling_header(fn_name) + + # Handle type conversions and arguments + params = [param.construct('standard') for param in prototype.params] + out.dump(prototype.signature('standard', fn_name, enable_count=enable_count)) + out.dump('{') + lines = [] + return_type = prototype.return_type.construct('standard') + lines.append(f'{return_type.tmp_type_text()} ret_value;') + for param in params: + if param.init_code: + lines.extend(param.init_code) + pass_args = ', '.join(param.argument for param in params) + lines.append(f'ret_value = {internal_fn}({pass_args});') + for param in params: + if param.final_code: + lines.extend(param.final_code) + lines.extend(return_type.return_code('ret_value')) + + # Indent the lines + lines = util.indent_lines(lines, 4 * ' ', start=1) + for line in lines: + out.dump(line) + out.dump('}') + + generate_function(template.prototype, base_name, internal_name) + if template.prototype.need_bigcount: + base_name_c = f'{base_name}_c' + generate_function(template.prototype, base_name_c, internal_name, + enable_count=True) + + +def generate_header(args, out): + """Generate an ABI header and conversion code.""" + out.dump(f'/* {consts.GENERATED_MESSAGE} */') + prototypes = [SourceTemplate.load(file_, args.srcdir).prototype for file_ in args.file] + builder = ABIHeaderBuilder(prototypes, out, external=args.external) + builder.dump_header() + + +def generate_source(args, out): + """Generate source file.""" + out.dump(f'/* {consts.GENERATED_MESSAGE} */') + template = SourceTemplate.load(args.source_file) + base_name = util.mpi_fn_name_from_base_fn_name(template.prototype.name) + if args.type == 'ompi': + ompi_abi(base_name, template, out) + else: + standard_abi(base_name, template, out) diff --git a/ompi/mpi/bindings/ompi_bindings/c_type.py b/ompi/mpi/bindings/ompi_bindings/c_type.py new file mode 100644 index 00000000000..0fdbede0b04 --- /dev/null +++ b/ompi/mpi/bindings/ompi_bindings/c_type.py @@ -0,0 +1,514 @@ +from abc import ABC, abstractmethod +from ompi_bindings.consts import ConvertFuncs + + +class Type(ABC): + """Type representation.""" + + PARAMS_OMPI_ABI = {} + + PARAMS_STANDARD_ABI = {} + + def __init__(self, type_, name=None, + mangle_name=lambda name: abi_internal_name(name), + count_param=None, **kwargs): + self.type = type_ + self.name = name + self.count_param = count_param + self.mangle_name = mangle_name + + @staticmethod + def construct(abi_type, type_, **kwargs): + """Construct the parameter for the given ABI and type.""" + if abi_type == 'ompi': + return Type.PARAMS_OMPI_ABI[type_](type_, **kwargs) + elif abi_type == 'standard': + return Type.PARAMS_STANDARD_ABI[type_](type_, **kwargs) + else: + raise RuntimeError(f'invalid ABI type {abi_type}') + + @staticmethod + def add_type(type_name, abi_type=('ompi', 'standard')): + """Add a new class corresponding to a type.""" + def wrapper(class_): + if 'ompi' in abi_type: + Type.PARAMS_OMPI_ABI[type_name] = class_ + if 'standard' in abi_type: + Type.PARAMS_STANDARD_ABI[type_name] = class_ + # Parameter.TYPES[type_] = class_ + return class_ + return wrapper + + @property + def is_count(self): + """Return True if this parameter is a count (requiring bigcount API).""" + return False + + @property + def init_code(self): + """Return the initialization code needed for an ABI wrapper.""" + return [] + + @property + def final_code(self): + """Return the finalization code needed for an ABI wrapper.""" + return [] + + def return_code(self, name): + """Process a value and then build up a return statement.""" + return [f'return {name};'] + + @property + def argument(self): + """Return the argument text required for passing an argument to a function.""" + return self.name + + @abstractmethod + def type_text(self, enable_count=False): + """Return the source text corresponding to a type definition.""" + + def tmp_type_text(self, enable_count=False): + """Return source text corresponding to a temporary type definition before conversion.""" + return self.type_text(enable_count=enable_count) + + def parameter(self, enable_count=False, **kwargs): + return f'{self.type_text(enable_count=enable_count)} {self.name}' + + +@Type.add_type('ERROR_CLASS') +class TypeErrorClass(Type): + + def type_text(self, enable_count=False): + return 'int' + + def return_code(self, name): + return [f'return {ConvertFuncs.ERROR_CLASS}({name});'] + + +@Type.add_type('BUFFER') +class TypeBuffer(Type): + + def type_text(self, enable_count=False): + return 'const void *' + + +@Type.add_type('BUFFER_OUT') +class TypeBufferOut(Type): + + def type_text(self, enable_count=False): + return f'void *' + + +@Type.add_type('COUNT') +class TypeCount(Type): + + @property + def is_count(self): + return True + + def type_text(self, enable_count=False): + return 'MPI_Count' if enable_count else 'int' + + +@Type.add_type('COUNT_ARRAY') +class TypeCount(Type): + """Array of counts (either int or MPI_Count).""" + + @property + def is_count(self): + return True + + def type_text(self, enable_count=False): + return 'MPI_Count *' if enable_count else 'int *' + + def parameter(self, enable_count=False): + count_type = 'MPI_Count' if enable_count else 'int' + return f'const {count_type} {self.name}[]' + + +@Type.add_type('DISPL_ARRAY') +class TypeCount(Type): + + @property + def is_count(self): + return True + + def type_text(self, enable_count=False): + return 'MPI_Aint *' if enable_count else 'int *' + + def parameter(self, enable_count=False): + count_type = 'MPI_Aint' if enable_count else 'int' + return f'const {count_type} {self.name}[]' + + +@Type.add_type('INT') +class TypeBufferOut(Type): + + def type_text(self, enable_count=False): + return 'int' + + +@Type.add_type('AINT') +class TypeBufferOut(Type): + + def type_text(self, enable_count=False): + return 'MPI_Aint' + + +@Type.add_type('INT_OUT') +class TypeBufferOut(Type): + + def type_text(self, enable_count=False): + return 'int *' + + def parameter(self, enable_count=False, **kwargs): + if self.count_param is None: + return f'int *{self.name}' + else: + return f'int {self.name}[]' + + +@Type.add_type('DOUBLE') +class TypeDouble(Type): + + def type_text(self, enable_count=False): + return 'double' + + +@Type.add_type('ARGV') +class TypeArgv(Type): + + def type_text(self, enable_count=False): + return 'char ***' + + +@Type.add_type('DATATYPE', abi_type=['ompi']) +class TypeDatatype(Type): + + def type_text(self, enable_count=False): + return 'MPI_Datatype' + + +@Type.add_type('DATATYPE_ARRAY', abi_type=['ompi']) +class TypeDatatypeArray(Type): + + def type_text(self, enable_count=False): + return 'MPI_Datatype' + + def parameter(self, enable_count=False): + return f'const {self.type_text(enable_count=enable_count)} {self.name}[]' + + +class StandardABIType(Type): + + @property + def tmpname(self): + return f'{self.name}_tmp' + + @property + def argument(self): + return self.tmpname + + +@Type.add_type('DATATYPE', abi_type=['standard']) +class TypeDatatype(StandardABIType): + + @property + def init_code(self): + return [f'MPI_Datatype {self.tmpname} = {ConvertFuncs.DATATYPE}({self.name});'] + + def type_text(self, enable_count=False): + return self.mangle_name('MPI_Datatype') + + +@Type.add_type('OP', abi_type=['ompi']) +class TypeDatatype(Type): + + def type_text(self, enable_count=False): + return 'MPI_Op' + + +@Type.add_type('OP', abi_type=['standard']) +class TypeDatatype(StandardABIType): + + @property + def init_code(self): + return [f'MPI_Op {self.tmpname} = {ConvertFuncs.OP}({self.name});'] + + def type_text(self, enable_count=False): + return self.mangle_name('MPI_Op') + + +@Type.add_type('RANK') +class TypeRank(Type): + + def type_text(self, enable_count=False): + return 'int' + + +@Type.add_type('TAG') +class TypeRank(Type): + + def type_text(self, enable_count=False): + return 'int' + + +@Type.add_type('COMM', abi_type=['ompi']) +class TypeCommunicator(Type): + + def type_text(self, enable_count=False): + return 'MPI_Comm' + + +@Type.add_type('COMM', abi_type=['standard']) +class TypeCommunicatorStandard(StandardABIType): + + @property + def init_code(self): + return [f'MPI_Comm {self.tmpname} = {ConvertFuncs.COMM}({self.name});'] + + def tmp_type_text(self, enable_count=False): + return 'MPI_Comm' + + def return_code(self, name): + return [f'return {ConvertOMPIToStandard.COMM}({name});'] + + def type_text(self, enable_count=False): + return self.mangle_name('MPI_Comm') + + +@Type.add_type('COMM_OUT', abi_type=['ompi']) +class TypeCommunicator(Type): + + def type_text(self, enable_count=False): + return 'MPI_Comm *' + + +@Type.add_type('COMM_OUT', abi_type=['standard']) +class TypeCommunicator(Type): + + @property + def final_code(self): + return [f'*{self.name} = {ConvertOMPIToStandard.COMM}((MPI_Comm) *{self.name});'] + + def type_text(self, enable_count=False): + type_name = self.mangle_name('MPI_Comm') + return f'{type_name} *' + + @property + def argument(self): + return f'(MPI_Comm *) {self.name}' + + +@Type.add_type('WIN', abi_type=['ompi']) +class TypeWindow(Type): + + def type_text(self, enable_count=False): + return 'MPI_Win' + + +@Type.add_type('WIN', abi_type=['standard']) +class TypeWindowStandard(StandardABIType): + + @property + def init_code(self): + return [f'MPI_Win {self.tmpname} = {ConvertFuncs.WIN}({self.name});'] + + def type_text(self, enable_count=False): + return self.mangle_name('MPI_Win') + + +@Type.add_type('REQUEST', abi_type=['ompi']) +class TypeRequest(Type): + + def type_text(self, enable_count=False): + return 'MPI_Request' + + +@Type.add_type('REQUEST', abi_type=['standard']) +class TypeRequestStandard(Type): + + def type_text(self, enable_count=False): + return self.mangle_name('MPI_Request') + + @property + def argument(self): + return f'(MPI_Request) {self.name}' + + +@Type.add_type('REQUEST_INOUT', abi_type=['ompi']) +class TypeRequestInOut(Type): + + def type_text(self, enable_count=False): + return 'MPI_Request *' + + +@Type.add_type('REQUEST_INOUT', abi_type=['standard']) +class TypeRequestInOutStandard(Type): + + @property + def final_code(self): + if self.count_param is None: + return [f'{ConvertFuncs.REQUEST}({self.name});'] + else: + return [ + 'for (int i = 0; i < %s; ++i) {' % (self.count_param,), + f'{ConvertFuncs.REQUEST}(&{self.name}[i]);', + '}', + ] + + @property + def argument(self): + return f'(MPI_Request *) {self.name}' + + def type_text(self, enable_count=False): + type_name = self.mangle_name('MPI_Request') + return f'{type_name} *' + + def parameter(self, enable_count=False, **kwargs): + type_name = self.mangle_name('MPI_Request') + if self.count_param is None: + return f'{type_name} *{self.name}' + else: + return f'{type_name} {self.name}[]' + + +@Type.add_type('STATUS_OUT', abi_type=['ompi']) +class TypeStatusOut(Type): + + def type_text(self, enable_count=False): + return 'MPI_Status *' + + def parameter(self, enable_count=False, **kwargs): + if self.count_param is None: + return f'MPI_Status *{self.name}' + else: + return f'MPI_Status {self.name}[]' + + +@Type.add_type('STATUS_OUT', abi_type=['standard']) +class TypeStausOutStandard(StandardABIType): + + def if_should_set_status(self): + """Generate the condition to check if the status(es) should be set.""" + condition = ' && '.join(f'{self.mangle_name(const)} != {self.name}' + for const in IGNORED_STATUS_HANDLES) + return 'if (%s) {' % (condition,) + + @property + def status_argument(self): + return f'{self.name}_arg' + + @property + def init_code(self): + code = [f'MPI_Status *{self.status_argument} = NULL;'] + if self.count_param is None: + code.append(f'MPI_Status {self.tmpname};') + else: + code.append(f'MPI_Status *{self.tmpname} = NULL;') + code.append(self.if_should_set_status()) + if self.count_param is not None: + code.append(f'{self.tmpname} = malloc({self.count_param} * sizeof(MPI_Status));') + code.append(f'{self.status_argument} = {self.tmpname};') + else: + code.append(f'{self.status_argument} = &{self.tmpname};') + code.append('} else {') + if self.count_param is not None: + code.append(f'{self.status_argument} = MPI_STATUSES_IGNORE;') + else: + code.append(f'{self.status_argument} = MPI_STATUS_IGNORE;') + code.append('}') + return code + + @property + def final_code(self): + code = [self.if_should_set_status()] + if self.count_param is None: + code.append(f'{ConvertFuncs.STATUS}({self.name}, &{self.tmpname});') + else: + code.extend([ + 'for (int i = 0; i < %s; ++i) {' % (self.count_param,), + f'{ConvertFuncs.STATUS}(&{self.name}[i], &{self.tmpname}[i]);', + '}', + f'free({self.tmpname});', + ]) + code.append('}') + return code + + @property + def argument(self): + return self.status_argument + + def type_text(self, enable_count=False): + type_name = self.mangle_name('MPI_Status') + return f'{type_name} *' + + def parameter(self, enable_count=False, **kwargs): + type_name = self.mangle_name('MPI_Status') + if self.count_param is None: + return f'{type_name} *{self.name}' + else: + return f'{type_name} {self.name}[]' + + +# For now this just assumes that MPI_Fint doesn't need any conversions +@Type.add_type('FINT') +class TypeFint(Type): + + def type_text(self, enable_count=False): + return 'MPI_Fint' + + +@Type.add_type('STRING') +class TypeString(Type): + + def type_text(self, enable_count=False): + return 'const char *' + + +@Type.add_type('STRING_OUT') +class TypeStringOut(Type): + + def type_text(self, enable_count=False): + return 'char *' + + +@Type.add_type('INFO', abi_type=['ompi']) +class TypeInfo(Type): + + def type_text(self, enable_count=False): + return 'MPI_Info' + + +@Type.add_type('INFO', abi_type=['standard']) +class TypeInfoStandard(StandardABIType): + + @property + def init_code(self): + return [f'MPI_Info {self.tmpname} = {ConvertFuncs.INFO}({self.name});'] + + def type_text(self, enable_count=False): + return self.mangle_name('MPI_Info') + + +@Type.add_type('FILE_OUT', abi_type=['ompi']) +class TypeFileOut(Type): + + def type_text(self, enable_count=False): + return 'MPI_File *' + + +@Type.add_type('FILE_OUT', abi_type=['standard']) +class TypeFileOutStandard(Type): + + @property + def argument(self): + return f'(MPI_File *) {self.name}' + + @property + def final_code(self): + return [f'{ConvertFuncs.FILE}({self.name});'] + + def type_text(self, enable_count=False): + type_name = self.mangle_name('MPI_File') + return f'{type_name} *' diff --git a/ompi/mpi/bindings/ompi_bindings/compiler.py.in b/ompi/mpi/bindings/ompi_bindings/compiler.py.in new file mode 100644 index 00000000000..132d080d978 --- /dev/null +++ b/ompi/mpi/bindings/ompi_bindings/compiler.py.in @@ -0,0 +1,15 @@ +# Copyright (c) 2024 Triad National Security, LLC. All rights +# reserved. +# +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ + +# Check if we have support for TS 29113 (templated at configure time) +HAVE_TS = '@OMPI_FORTRAN_HAVE_TS@' == '1' +OMPI_F08_IGNORE_TKR_PREDECL = '@OMPI_F08_IGNORE_TKR_PREDECL@' +OMPI_F08_IGNORE_TKR_TYPE = '@OMPI_F08_IGNORE_TKR_TYPE@' +OMPI_FORTRAN_IGNORE_TKR_PREDECL = '@OMPI_FORTRAN_IGNORE_TKR_PREDECL@' +OMPI_FORTRAN_IGNORE_TKR_TYPE = '@OMPI_FORTRAN_IGNORE_TKR_TYPE@' diff --git a/ompi/mpi/bindings/ompi_bindings/consts.py b/ompi/mpi/bindings/ompi_bindings/consts.py new file mode 100644 index 00000000000..8c8bb3f496d --- /dev/null +++ b/ompi/mpi/bindings/ompi_bindings/consts.py @@ -0,0 +1,269 @@ +# Copyright (c) 2024 Triad National Security, LLC. All rights +# reserved. +# +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +# +import re + +FORTRAN_ERROR_NAME = 'ierror' +C_ERROR_NAME = 'ierr' +C_ERROR_TMP_NAME = 'c_ierr' +GENERATED_MESSAGE = 'THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT BY HAND.' +PROTOTYPE_RE = re.compile( + r"""\.\w+\( # Subroutine name (and opening + # parenthesis); + (\s*\w+ # Type name (corresponding to + # classes deriving FortranType below); + \s+\w+\s* # Parameter name; + (\[\s*\w+\s*=\s*\w+\s* # Bracket key-value pairs to + (;\s*\w+\s*=\s*\w+\s*)*\])? # type ('[key0=value1;key1=value2...]'); + \s*,?)+ # Extra whitespace and comma; + # after parameter; + \) # Closing parenthesis; + (\s*:\s*(\w+=\w+\s*(;\s*\w+=\w+\s*)*))? # Trailing key-value pairs, to be + # passed to the prototype and type + # handling code; + """, + re.X | re.MULTILINE, +) + +# +# C and ABI constants +# +# C type: const int +ERROR_CLASSES = [ + 'MPI_SUCCESS', + 'MPI_ERR_BUFFER', + 'MPI_ERR_COUNT', + 'MPI_ERR_TYPE', + 'MPI_ERR_TAG', + 'MPI_ERR_COMM', + 'MPI_ERR_RANK', + 'MPI_ERR_REQUEST', + 'MPI_ERR_ROOT', + 'MPI_ERR_GROUP', + 'MPI_ERR_OP', + 'MPI_ERR_TOPOLOGY', + 'MPI_ERR_DIMS', + 'MPI_ERR_ARG', + 'MPI_ERR_UNKNOWN', + 'MPI_ERR_TRUNCATE', + 'MPI_ERR_OTHER', + 'MPI_ERR_INTERN', + 'MPI_ERR_PENDING', + 'MPI_ERR_IN_STATUS', + 'MPI_ERR_ACCESS', + 'MPI_ERR_AMODE', + 'MPI_ERR_ASSERT', + 'MPI_ERR_BAD_FILE', + 'MPI_ERR_BASE', + 'MPI_ERR_CONVERSION', + 'MPI_ERR_DISP', + 'MPI_ERR_DUP_DATAREP', + 'MPI_ERR_FILE_EXISTS', + 'MPI_ERR_FILE_IN_USE', + 'MPI_ERR_FILE', + 'MPI_ERR_INFO_KEY', + 'MPI_ERR_INFO_NOKEY', + 'MPI_ERR_INFO_VALUE', + 'MPI_ERR_INFO', + 'MPI_ERR_IO', + 'MPI_ERR_KEYVAL', + 'MPI_ERR_LOCKTYPE', + 'MPI_ERR_NAME', + 'MPI_ERR_NO_MEM', + 'MPI_ERR_NOT_SAME', + 'MPI_ERR_NO_SPACE', + 'MPI_ERR_NO_SUCH_FILE', + 'MPI_ERR_PORT', + 'MPI_ERR_PROC_ABORTED', + 'MPI_ERR_QUOTA', + 'MPI_ERR_READ_ONLY', + 'MPI_ERR_RMA_ATTACH', + 'MPI_ERR_RMA_CONFLICT', + 'MPI_ERR_RMA_RANGE', + 'MPI_ERR_RMA_SHARED', + 'MPI_ERR_RMA_SYNC', + 'MPI_ERR_RMA_FLAVOR', + 'MPI_ERR_SERVICE', + 'MPI_ERR_SESSION', + 'MPI_ERR_SIZE', + 'MPI_ERR_SPAWN', + 'MPI_ERR_UNSUPPORTED_DATAREP', + 'MPI_ERR_UNSUPPORTED_OPERATION', + 'MPI_ERR_WIN', + 'MPI_T_ERR_CANNOT_INIT', + 'MPI_T_ERR_NOT_INITIALIZED', + 'MPI_T_ERR_MEMORY', + 'MPI_T_ERR_INVALID', + 'MPI_T_ERR_INVALID_INDEX', + 'MPI_T_ERR_INVALID_ITEM', + 'MPI_T_ERR_INVALID_SESSION', + 'MPI_T_ERR_INVALID_HANDLE', + 'MPI_T_ERR_INVALID_NAME', + 'MPI_T_ERR_OUT_OF_HANDLES', + 'MPI_T_ERR_OUT_OF_SESSIONS', + 'MPI_T_ERR_CVAR_SET_NOT_NOW', + 'MPI_T_ERR_CVAR_SET_NEVER', + 'MPI_T_ERR_PVAR_NO_WRITE', + 'MPI_T_ERR_PVAR_NO_STARTSTOP', + 'MPI_T_ERR_PVAR_NO_ATOMIC', + 'MPI_ERR_LASTCODE', +] + +PREDEFINED_DATATYPES = [ + 'MPI_CHAR', + 'MPI_SHORT', + 'MPI_INT', + 'MPI_LONG', + 'MPI_LONG_LONG_INT', + 'MPI_LONG_LONG', + 'MPI_SIGNED_CHAR', + 'MPI_UNSIGNED_CHAR', + 'MPI_UNSIGNED_SHORT', + 'MPI_UNSIGNED', + 'MPI_UNSIGNED_LONG', + 'MPI_UNSIGNED_LONG_LONG', + 'MPI_FLOAT', + 'MPI_DOUBLE', + 'MPI_LONG_DOUBLE', + 'MPI_WCHAR', + 'MPI_C_BOOL', + 'MPI_INT8_T', + 'MPI_INT16_T', + 'MPI_INT32_T', + 'MPI_INT64_T', + 'MPI_UINT8_T', + 'MPI_UINT16_T', + 'MPI_UINT32_T', + 'MPI_UINT64_T', + 'MPI_AINT', + 'MPI_COUNT', + 'MPI_OFFSET', + 'MPI_C_COMPLEX', + 'MPI_C_FLOAT_COMPLEX', + 'MPI_C_DOUBLE_COMPLEX', + 'MPI_C_LONG_DOUBLE_COMPLEX', + 'MPI_BYTE', + 'MPI_PACKED', + 'MPI_CXX_BOOL', + 'MPI_CXX_FLOAT_COMPLEX', + 'MPI_CXX_DOUBLE_COMPLEX', + 'MPI_CXX_LONG_DOUBLE_COMPLEX', + 'MPI_FLOAT_INT', + 'MPI_DOUBLE_INT', + 'MPI_LONG_INT', + 'MPI_2INT', + 'MPI_SHORT_INT', + 'MPI_LONG_DOUBLE_INT', +] + +# C type: MPI_Comm +RESERVED_COMMUNICATORS = [ + 'MPI_COMM_NULL', + 'MPI_COMM_WORLD', + 'MPI_COMM_SELF', +] + +COMMUNICATOR_SPLIT_TYPES = [ + 'MPI_COMM_TYPE_SHARED', + 'MPI_COMM_TYPE_HW_UNGUIDED', + 'MPI_COMM_TYPE_HW_GUIDED', +] + +RESERVED_WINDOWS = [ + 'MPI_WIN_NULL', +] + +RESERVED_REQUESTS = [ + 'MPI_REQUEST_NULL', +] + +RESERVED_INFOS = [ + 'MPI_INFO_ENV', + 'MPI_INFO_NULL', +] + +RESERVED_FILES = [ + 'MPI_FILE_NULL', +] + +IGNORED_STATUS_HANDLES = [ + 'MPI_STATUSES_IGNORE', + 'MPI_STATUS_IGNORE', +] + +COLLECTIVE_OPERATIONS = [ + 'MPI_MAX', + 'MPI_MIN', + 'MPI_SUM', + 'MPI_PROD', + 'MPI_MAXLOC', + 'MPI_MINLOC', + 'MPI_BAND', + 'MPI_BOR', + 'MPI_BXOR', + 'MPI_LAND', + 'MPI_LOR', + 'MPI_LXOR', + 'MPI_REPLACE', + 'MPI_NO_OP', +] + +VARIOUS_CONSTANTS = { + # Just setting this to the same as ompi ABI for right now, but will need to + # match the standard ABI value when defined + 'MPI_MAX_LIBRARY_VERSION_STRING': 256, + 'MPI_MAX_PROCESSOR_NAME': 256, +} + +# Types + +C_OPAQUE_TYPES = { + 'MPI_Aint': 'intptr_t', + 'MPI_Offset': 'int64_t', + 'MPI_Count': 'size_t', + # The below type needs to be set externally depending on Fortran compiler + 'MPI_Fint': 'int64_t', +} + +C_HANDLES = [ + 'MPI_Comm', + 'MPI_Datatype', + 'MPI_Errhandler', + 'MPI_File', + 'MPI_Group', + 'MPI_Info', + 'MPI_Message', + 'MPI_Op', + 'MPI_Request', + 'MPI_Session', + 'MPI_Win', +] + +class ConvertFuncs: + """Names of conversion functions (between standard ABI and OMPI ABI).""" + + ERROR_CLASS = 'ompi_convert_intern_error_abi_error' + COMM = 'ompi_convert_abi_comm_intern_comm' + DATATYPE = 'ompi_convert_abi_datatype_intern_datatype' + REQUEST = 'ompi_convert_abi_request_intern_request' + STATUS = 'ompi_convert_intern_status_abi_status' + OP = 'ompi_convert_abi_op_intern_op' + WIN = 'ompi_convert_abi_win_intern_win' + INFO = 'ompi_convert_abi_info_intern_info' + FILE = 'ompi_convert_abi_file_intern_file' + + +class ConvertOMPIToStandard: + """Generated function for converting from OMPI to standard ABI.""" + + COMM = 'ompi_convert_comm_ompi_to_standard' + + +# Inline function attributes +INLINE_ATTRS = '__opal_attribute_always_inline__ static inline' diff --git a/ompi/mpi/bindings/ompi_bindings/fortran.py b/ompi/mpi/bindings/ompi_bindings/fortran.py new file mode 100644 index 00000000000..47e02201acb --- /dev/null +++ b/ompi/mpi/bindings/ompi_bindings/fortran.py @@ -0,0 +1,399 @@ +# Copyright (c) 2024 Triad National Security, LLC. All rights +# reserved. +# +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +# +"""Fortran binding generation code. + +This takes as input a *.in file containing a list of prototypes for Fortran +subroutines with generic types. Using this file, it can generate the Fortran +subroutines in one file and the C wraping code in another for all prototypes +listed. +""" +from collections import namedtuple +import re +from ompi_bindings import compiler, consts, util +from ompi_bindings.fortran_type import FortranType + + +FortranParameter = namedtuple('FortranParameter', ['type_name', 'name', 'dep_params']) +FortranPrototype = namedtuple('FortranPrototype', ['fn_name', 'lno', 'parameters']) + + +def parse_prototype(lno, line): + """Parse a prototype for the given line and string.""" + if consts.PROTOTYPE_RE.match(line) is None: + raise util.BindingError( + f'Invalid function prototype for Fortran interface starting on line {lno}' + ) + + start = line.index('(') + end = line.index(')') + fn_name = line[1:start].strip() + parameters = line[start+1:end].split(',') + + # Attempt to parse each parameter + parsed_parameters = [] + try: + for param in parameters: + param = param.strip() + param_parts = param.split() + type_name = param_parts[0] + name = ''.join(param_parts[1:]) + type_ = FortranType.get(type_name) + dep_params = None + + # Check for 'param[name=param(;name=param)]' parameters, + # indicating a dependency on that other parameter + if '[' in name: + idx = name.index('[') + dep_params = [part.split('=') for part in name[idx+1:-1].split(';')] + dep_params = dict(dep_params) + name = name[:idx] + + # Validate the parameter key values (or an empty list if not found) + type_.validate_dep_param_keys(name, [] if dep_params is None else dep_params.keys()) + + parsed_parameters.append(FortranParameter(type_name, name, dep_params)) + return FortranPrototype(fn_name, lno, parsed_parameters) + except util.BindingError as err: + raise util.BindingError( + f'Failed to parse prototype starting on line {lno}: {err}' + ) from None + except KeyError as err: + raise util.BindingError( + f'Found invalid type starting on line {lno}: {err}' + ) from None + + +def load_prototypes(fname): + """Load the prototypes from a file.""" + with open(fname) as fp: + tmp_proto_string = [] + proto_strings = [] + cur_lno = 0 + reading_proto = False + + # The loop below is designed to read in each prototype, each delimited + # by a '.' preceding the name of the subroutine, allowing for the + # prototype to run across multiple lines. + for i, line in enumerate(fp): + lno = i + 1 + line = line.strip() + + # First strip comments + comm_idx = line.find('#') + if comm_idx != -1: + line = line[:comm_idx] + if not line: + continue + + # Set the current line for the prototype + if not tmp_proto_string: + cur_lno = lno + + # Check for initial '.' character, indicating the start of a prototype + reading_proto = reading_proto or line[0] == '.' + if line[0] == '.' and tmp_proto_string: + # If the buffer is not empty, then the previous prototype + # string is complete and can be saved + proto_strings.append((cur_lno, ' '.join(tmp_proto_string))) + cur_lno = lno + tmp_proto_string = [] + # Only add the line to the current buffer if we already encountered + # a '.' at the start of this or a previous line + if reading_proto: + tmp_proto_string.append(line) + + if tmp_proto_string: + proto_strings.append((cur_lno, ' '.join(tmp_proto_string))) + + return [parse_prototype(lno, proto_string) for lno, proto_string in proto_strings] + + +class FortranBinding: + """Class for generating the binding for a single function.""" + + def __init__(self, prototype, out, bigcount=False): + self.bigcount = bigcount + self.fn_name = prototype.fn_name + self.out = out + self.parameters = [] + param_map = {} + dep_params = {} + for param in prototype.parameters: + type_ = FortranType.get(param.type_name) + param_type = type_(param.name, self.fn_name, bigcount=bigcount) + self.parameters.append(param_type) + param_map[param.name] = param_type + if param.dep_params is not None: + dep_params[param.name] = param.dep_params + # Set dependent parameters for those that need them + try: + for name, deps in dep_params.items(): + param_map[name].dep_params = {key: param_map[dep_name] for key, dep_name in deps.items()} + except KeyError as err: + raise util.BindingError( + f'Invalid dependent type in prototype starting on line {prototype.lno}: {err}' + ) + + def dump(self, *pargs, **kwargs): + """Write to the output file.""" + self.out.dump(*pargs, **kwargs) + + def _fn_name_suffix(self): + """Return a suffix for function names.""" + return '_c' if self.bigcount else '' + + @property + def c_func_name(self): + """Produce the final C func name from base_name.""" + return f'ompi_{self.fn_name}_wrapper_f08{self._fn_name_suffix()}' + + def _use(self): + """Determine the Fortran use-statements needed.""" + use = {} + for param in self.parameters: + for mod, name in param.use(): + if mod not in use: + use[mod] = set() + use[mod].add(name) + return use + + def _use_stmts(self): + """Return a list of required use statments.""" + use = self._use() + stmts = [] + for mod, names in use.items(): + names = ', '.join(names) + stmts.append(f'use :: {mod}, only: {names}') + return stmts + + def _print_fortran_interface(self): + """Output the C subroutine binding for the Fortran code.""" + name = self.c_func_name + self.dump(' interface') + + # Print the subroutine and parameter list, breaking parameters across lines + subroutine_start = f' subroutine {name}(' + params = [param.name for param in self.parameters] + params.append(consts.FORTRAN_ERROR_NAME) + lines = util.break_param_lines_fortran(start=subroutine_start, params=params, end=') &') + for line in lines: + self.dump(line) + self.dump(f' BIND(C, name="{name}")') + + use_stmts = self._use_stmts() + for stmt in use_stmts: + self.dump(f' {stmt}') + self.dump(' implicit none') + for param in self.parameters: + self.dump(f' {param.declare_cbinding_fortran()}') + self.dump(f' INTEGER, INTENT(OUT) :: {consts.FORTRAN_ERROR_NAME}') + self.dump(f' end subroutine {name}') + self.dump(' end interface') + + def _print_fortran_header(self, is_interface=False): + """Print the header, including use stmts, dummy variable decls, etc.. + + This does not include the subroutine line. + """ + # Use statements + use_stmts = self._use_stmts() + for stmt in use_stmts: + self.dump(f' {stmt}') + self.dump(' implicit none') + # Parameters/dummy variable declarations + for param in self.parameters: + if is_interface: + self.dump_lines(param.interface_predeclare()) + self.dump_lines(param.declare()) + # Add the integer error manually + self.dump(f' INTEGER, OPTIONAL, INTENT(OUT) :: {consts.FORTRAN_ERROR_NAME}') + + def _print_fortran_subroutine(self): + """Output the Fortran subroutine line.""" + sub_name = util.fortran_f08_name(self.fn_name, bigcount=self.bigcount) + params = [param.name for param in self.parameters] + params.append(consts.FORTRAN_ERROR_NAME) + lines = util.break_param_lines_fortran(f'subroutine {sub_name}(', params, ')') + for line in lines: + self.dump(line) + + def _print_fortran_subroutine_end(self): + """Output the Fortran end subroutine line.""" + sub_name = util.fortran_f08_name(self.fn_name, bigcount=self.bigcount) + self.dump(f'end subroutine {sub_name}') + + def dump_lines(self, line_text): + for line in line_text.split('\n'): + line = line.rstrip() + if line: + self.dump(f' {line}') + + def print_f_source(self): + """Output the main MPI Fortran subroutine.""" + self._print_fortran_subroutine() + self._print_fortran_header() + + # Temporaries + self.dump(f' INTEGER :: {consts.C_ERROR_TMP_NAME}') + for param in self.parameters: + self.dump_lines(param.declare_tmp()) + + # Interface for call to C function + self.dump() + self._print_fortran_interface() + self.dump() + + # Call into the C function + call_start = f' call {self.c_func_name}(' + params = [param.argument() for param in self.parameters] + params.append(consts.C_ERROR_TMP_NAME) + lines = util.break_param_lines_fortran(start=call_start, params=params, end=')') + for line in lines: + self.dump(line) + + # Convert error type + self.dump(f' if (present({consts.FORTRAN_ERROR_NAME})) {consts.FORTRAN_ERROR_NAME} = {consts.C_ERROR_TMP_NAME}') + + for param in self.parameters: + self.dump_lines(param.post()) + + self._print_fortran_subroutine_end() + + def print_c_source(self): + """Output the C source and function that the Fortran calls into.""" + parameters = [param.c_parameter() for param in self.parameters] + # Always append the integer error + parameters.append(f'MPI_Fint *{consts.C_ERROR_NAME}') + parameters = ', '.join(parameters) + # Just put the signature here to silence `-Wmissing-prototypes` + c_func = self.c_func_name + self.dump(f'void {c_func}({parameters});') + self.dump(f'void {c_func}({parameters})') + self.dump('{') + self.dump(f' int {consts.C_ERROR_TMP_NAME}; ') + + # First the temporary declarations + for param in self.parameters: + self.dump_lines(param.c_declare_tmp()) + + # Shortcut conditions, if any + for param in self.parameters: + condition = param.c_shortcut_condition() + if condition is None: + continue + self.dump(f' if ({condition}) {{') + self.dump(f' *{consts.C_ERROR_NAME} = OMPI_INT_2_FINT(MPI_SUCCESS);') + for other_param in self.parameters: + self.dump_lines(other_param.c_shortcut_code()) + self.dump(' return;') + self.dump(' }') + + # Prepare code for temporaries, etc. + for param in self.parameters: + self.dump_lines(param.c_prepare()) + + # Call into the C API + c_api_func = util.ext_api_func_name_profile(self.fn_name, bigcount=self.bigcount) + arguments = [param.c_argument() for param in self.parameters] + arguments = ', '.join(arguments) + self.dump(f' {consts.C_ERROR_TMP_NAME} = {c_api_func}({arguments});') + + # Post-processing code + self.dump(f' *{consts.C_ERROR_NAME} = OMPI_INT_2_FINT({consts.C_ERROR_TMP_NAME});') + for param in self.parameters: + self.dump_lines(param.c_post()) + self.dump('}') + + def print_interface(self): + """Output just the Fortran interface for this binding.""" + self._print_fortran_subroutine() + self._print_fortran_header(is_interface=True) + self._print_fortran_subroutine_end() + + +def print_f_source_header(out): + """Print the fortran f08 file header.""" + out.dump(f'! {consts.GENERATED_MESSAGE}') + out.dump('#include "ompi/mpi/fortran/configure-fortran-output.h"') + + +def print_profiling_rename_macros(prototypes, out): + """Print macros for renaming functions for the profiling interface. + + Previously hardcoded in mpi-f08-rename.h. + """ + out.dump('#if OMPI_BUILD_MPI_PROFILING') + for prototype in prototypes: + name = util.fortran_f08_name(prototype.fn_name) + out.dump(f'#define {name} P{name}') + # Check for bigcount version + if util.fortran_prototype_has_bigcount(prototype): + bigcount_name = util.fortran_f08_name(prototype.fn_name, bigcount=True) + out.dump(f'#define {bigcount_name} P{bigcount_name}') + out.dump('#endif /* OMPI_BUILD_MPI_PROFILING */') + + +def print_c_source_header(out): + """Print the header of the C source file.""" + out.dump(f'/* {consts.GENERATED_MESSAGE} */') + if compiler.HAVE_TS: + out.dump('#include ') + out.dump('#include "ts.h"') + out.dump('#include "ompi_config.h"') + out.dump('#include "mpi.h"') + out.dump('#include "ompi/errhandler/errhandler.h"') + out.dump('#include "ompi/mpi/fortran/mpif-h/status-conversion.h"') + out.dump('#include "ompi/mpi/fortran/base/constants.h"') + out.dump('#include "ompi/mpi/fortran/base/fint_2_int.h"') + out.dump('#include "ompi/request/request.h"') + out.dump('#include "ompi/communicator/communicator.h"') + + +def print_binding(prototype, lang, out, bigcount=False): + """Print the binding with or without bigcount.""" + binding = FortranBinding(prototype, out=out, bigcount=bigcount) + if lang == 'fortran': + binding.print_f_source() + else: + binding.print_c_source() + + +def generate_code(args, out): + """Generate binding code based on arguments.""" + prototypes = load_prototypes(args.template) + if args.lang == 'fortran': + print_f_source_header(out) + out.dump() + print_profiling_rename_macros(prototypes, out) + out.dump() + else: + print_c_source_header(out) + for prototype in prototypes: + out.dump() + print_binding(prototype, args.lang, out) + if util.fortran_prototype_has_bigcount(prototype): + out.dump() + print_binding(prototype, args.lang, bigcount=True, out=out) + + +def generate_interface(args, out): + """Generate the Fortran interface files.""" + prototypes = load_prototypes(args.template) + out.dump(f'! {consts.GENERATED_MESSAGE}') + for prototype in prototypes: + ext_name = util.ext_api_func_name(prototype.fn_name) + out.dump(f'interface {ext_name}') + binding = FortranBinding(prototype, out=out) + binding.print_interface() + if util.fortran_prototype_has_bigcount(prototype): + out.dump() + binding_c = FortranBinding(prototype, out=out, bigcount=True) + binding_c.print_interface() + out.dump(f'end interface {ext_name}') diff --git a/ompi/mpi/bindings/ompi_bindings/fortran_type.py b/ompi/mpi/bindings/ompi_bindings/fortran_type.py new file mode 100644 index 00000000000..1f37a782459 --- /dev/null +++ b/ompi/mpi/bindings/ompi_bindings/fortran_type.py @@ -0,0 +1,881 @@ +# Copyright (c) 2024 Triad National Security, LLC. All rights +# reserved. +# +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +# +"""Fortran type base class.""" +from abc import ABC, abstractmethod +from ompi_bindings import compiler, consts, util + + +class FortranType(ABC): + + def __init__(self, name, fn_name, bigcount=False, **kwargs): + self.name = name + self.fn_name = fn_name + self.bigcount = bigcount + # List of dependent type/parameters, such as for counts + self.dep_params = None + self.used_counters = 0 + + TYPES = {} + + @classmethod + def add(cls, type_name): + """Decorator for adding types.""" + def wrapper(class_): + cls.TYPES[type_name] = class_ + return class_ + return wrapper + + @classmethod + def get(cls, type_name): + return cls.TYPES[type_name] + + @staticmethod + def validate_dep_param_keys(param_name, keys): + """Validate the keys that are allowed to be used for a dependent param.""" + # No dependent parameters allowed by default + if keys: + raise util.FortranBindingError( + f'Invalid keys found for parameter {param_name}: {list(keys)}' + ) + + @property + def fn_api_name(self): + """Return the MPI API name to be used in error messages, etc..""" + return util.ext_api_func_name(self.fn_name, bigcount=self.bigcount).upper() + + @property + def tmp_name(self): + """Return a temporary name for use in C.""" + return f'c_{self.name}' + + @property + def tmp_name2(self): + """Return a secondary temporary name for use in C.""" + return f'c_{self.name}2' + + def tmp_counter(self): + """Get a temporary counter variable to be used in a loop.""" + name = f'{self.name}_i_{self.used_counters}' + self.used_counters += 1 + return name + + def interface_predeclare(self): + """Return predeclaration code, if required for the interface.""" + return '' + + @abstractmethod + def declare(self): + """Return a declaration for the type.""" + + def declare_tmp(self): + """Declare temporaries on in the subroutine.""" + return '' + + def declare_cbinding_fortran(self): + """Return the C binding declaration as seen from Fortran.""" + return self.declare() + + def argument(self): + """Return the value to pass as an argument.""" + return self.name + + def use(self): + """Return list of (module, name) for a Fortran use-statement.""" + return [] + + def post(self): + """Return post-processing code to be run after the call.""" + return '' + + @abstractmethod + def c_parameter(self): + """Return the parameter expression to be used in the C function.""" + + def c_declare_tmp(self): + """Code to declare temporary variables for conversions, etc..""" + return '' + + def c_shortcut_condition(self): + """Shortcut conditional code. + + If the conditional evaluates to true in C, then code defined in + c_shortcut_code() for all other parameters will be run and the + underlying C function will not be called. + """ + return None + + def c_shortcut_code(self): + """Shortcut code to run if a parameter defines a shortcut condition.""" + return '' + + def c_prepare(self): + """Code to be called before being passed to underlying C function.""" + return '' + + def c_argument(self): + """Return the value to pass as an argument in the C code.""" + return self.name + + def c_post(self): + """Code to be run after a call to the underlying C function.""" + return '' + +# +# Definitions of generic types in Fortran and how these can be converted +# to and from C. +# + +@FortranType.add('BUFFER') +class BufferType(FortranType): + @staticmethod + def validate_dep_param_keys(param_name, keys): + util.validate_allowed_keys(keys, ['count', 'type', 'comm'], 'BUFFER', param_name) + + def interface_predeclare(self): + return f'{compiler.OMPI_F08_IGNORE_TKR_PREDECL} {self.name}' + + def declare(self): + return f'{compiler.OMPI_F08_IGNORE_TKR_TYPE}, INTENT(IN) :: {self.name}' + + if compiler.HAVE_TS: + def c_parameter(self): + return f'CFI_cdesc_t *{self.name}' + + @property + def tmp_datatype1(self): + return self.dep_params['type'].tmp_name + + @property + def tmp_datatype2(self): + return self.dep_params['type'].tmp_name2 + + @property + def tmp_count(self): + return self.dep_params['count'].tmp_name + + @property + def tmp_comm(self): + return self.dep_params['comm'].tmp_name + + def c_declare_tmp(self): + type_name = self.dep_params['type'].name + count_name = self.dep_params['count'].name + if self.bigcount: + count_init = f'MPI_Count {self.tmp_count} = *{count_name};' + else: + count_init = f'int {self.tmp_count} = OMPI_FINT_2_INT(*{count_name});' + return util.prepare_text(f""" + void *{self.tmp_name} = {self.name}->base_addr; + {count_init} + MPI_Datatype {self.tmp_datatype1} = PMPI_Type_f2c(*{type_name}), {self.tmp_datatype2}; + """) + + def c_prepare(self): + type_name = self.dep_params['type'].name + return util.prepare_text(f""" + OMPI_CFI_2_C({self.name}, {self.tmp_count}, {self.tmp_datatype1}, {self.tmp_datatype2}, {consts.C_ERROR_TMP_NAME}); + if (MPI_SUCCESS != {consts.C_ERROR_TMP_NAME}) {{ + *{consts.C_ERROR_NAME} = OMPI_INT_2_FINT({consts.C_ERROR_TMP_NAME}); + OMPI_ERRHANDLER_INVOKE({self.tmp_comm}, {consts.C_ERROR_TMP_NAME}, "{self.fn_api_name}"); + return; + }}""") + + def c_argument(self): + return f'OMPI_F2C_BOTTOM({self.tmp_name})' + + def c_post(self): + return util.prepare_text(f""" + if ({self.tmp_datatype2} != {self.tmp_datatype1}) {{ + ompi_datatype_destroy(&{self.tmp_datatype2}); + }}""") + else: + def c_parameter(self): + return f'char *{self.name}' + + def c_argument(self): + return f'OMPI_F2C_BOTTOM({self.name})' + + +@FortranType.add('BUFFER_ASYNC') +class BufferAsyncType(BufferType): + def declare(self): + return f'{compiler.OMPI_F08_IGNORE_TKR_TYPE}, INTENT(IN) OMPI_ASYNCHRONOUS :: {self.name}' + + +@FortranType.add('BUFFER_OUT') +class BufferOutType(BufferType): + def declare(self): + return f'{compiler.OMPI_F08_IGNORE_TKR_TYPE} :: {self.name}' + + +@FortranType.add('BUFFER_ASYNC_OUT') +class BufferAsyncOutType(BufferType): + def declare(self): + return f'{compiler.OMPI_F08_IGNORE_TKR_TYPE} OMPI_ASYNCHRONOUS :: {self.name}' + + +@FortranType.add('VBUFFER') +class VBufferType(FortranType): + """Variable buffer type, as used by MPI_*v() functions.""" + @staticmethod + def validate_dep_param_keys(param_name, keys): + util.validate_allowed_keys(keys, ['counts', 'displs', 'type', 'comm'], 'VBUFFER', param_name) + + def declare(self): + return f'{compiler.OMPI_F08_IGNORE_TKR_TYPE}, INTENT(IN) :: {self.name}' + + if compiler.HAVE_TS: + def c_parameter(self): + return f'CFI_cdesc_t *{self.name}' + + def c_declare_tmp(self): + datatype = self.dep_params['type'] + # NOTE: Using tmp_name2 here for the datatype, since the datatype + # class assumes it will be used when TS support is enabled + return util.prepare_text(f""" + MPI_Datatype {datatype.tmp_name2} = NULL; + char *{self.tmp_name} = {self.name}->base_addr;""") + + def c_prepare(self): + comm = self.dep_params['comm'] + datatype = self.dep_params['type'].name + tmp_datatype = self.dep_params['type'].tmp_name2 + counts = self.dep_params['counts'].name + displs = self.dep_params['displs'].name + return util.prepare_text(f""" + if (OMPI_COMM_IS_INTER({comm.tmp_name}) || !OMPI_IS_FORTRAN_IN_PLACE({self.tmp_name})) {{ + {tmp_datatype} = PMPI_Type_f2c(*{datatype}); + OMPI_CFI_CHECK_CONTIGUOUS({self.name}, {consts.C_ERROR_TMP_NAME}); + if (MPI_SUCCESS != {consts.C_ERROR_TMP_NAME}) {{ + *{consts.C_ERROR_NAME} = OMPI_INT_2_FINT({consts.C_ERROR_TMP_NAME}); + OMPI_ERRHANDLER_INVOKE({comm.tmp_name}, {consts.C_ERROR_TMP_NAME}, "{self.fn_name}"); + return; + }} + OMPI_ARRAY_FINT_2_INT({counts}, {comm.size}); + OMPI_ARRAY_FINT_2_INT({displs}, {comm.size}); + }} else {{ + {self.tmp_name} = MPI_IN_PLACE; + }} + """) + + def c_argument(self): + return self.tmp_name + + def c_post(self): + counts = self.dep_params['counts'].name + displs = self.dep_params['displs'].name + return util.prepare_text(f""" + OMPI_ARRAY_FINT_2_INT_CLEANUP({counts}); + OMPI_ARRAY_FINT_2_INT_CLEANUP({displs});""") + else: + def c_parameter(self): + return f'char *{self.name}' + + def c_argument(self): + return f'OMPI_F2C_BOTTOM(OMPI_F2C_IN_PLACE({self.name}))' + + +@FortranType.add('VBUFFER_OUT') +class VBufferType(FortranType): + """Variable buffer receive type, as used by MPI_*v() functions.""" + @staticmethod + def validate_dep_param_keys(param_name, keys): + util.validate_allowed_keys(keys, ['type', 'comm'], 'VBUFFER_OUT', param_name) + + def declare(self): + return f'{compiler.OMPI_F08_IGNORE_TKR_TYPE} :: {self.name}' + + if compiler.HAVE_TS: + def c_parameter(self): + return f'CFI_cdesc_t *{self.name}' + + def c_declare_tmp(self): + datatype = self.dep_params['type'] + return util.prepare_text(f""" + MPI_Datatype {datatype.tmp_name2} = PMPI_Type_f2c(*{datatype.name}); + char *{self.tmp_name} = {self.name}->base_addr; + """) + + def c_prepare(self): + comm = self.dep_params['comm'] + return util.prepare_text(f""" + OMPI_CFI_CHECK_CONTIGUOUS({self.name}, {consts.C_ERROR_TMP_NAME}); + if (MPI_SUCCESS != {consts.C_ERROR_TMP_NAME}) {{ + *{consts.C_ERROR_NAME} = OMPI_INT_2_FINT({consts.C_ERROR_TMP_NAME}); + OMPI_ERRHANDLER_INVOKE({comm.tmp_name}, {consts.C_ERROR_TMP_NAME}, "{self.fn_name}"); + }} + """) + else: + def c_parameter(self): + return f'char *{self.name}' + + def c_argument(self): + name = self.tmp_name if compiler.HAVE_TS else self.name + return f'OMPI_F2C_BOTTOM({name})' + + +class WBufferBase(FortranType): + def c_prepare(self): + if compiler.HAVE_TS: + c_ierr = consts.C_ERROR_TMP_NAME + return util.prepare_text(f""" + OMPI_CFI_CHECK_CONTIGUOUS({self.name}, {c_ierr}); + if (MPI_SUCCESS != {c_ierr}) {{ + *{consts.C_ERROR_NAME} = OMPI_INT_2_FINT({c_ierr}); + OMPI_ERRHANDLER_INVOKE({self.dep_params['comm'].tmp_name}, {c_ierr}, "{self.fn_name}") + return; + }}""") + + return '' + + +@FortranType.add('WBUFFER') +class WBufferType(WBufferBase): + """Variable buffer send type, used with MPI_*w() functions.""" + @staticmethod + def validate_dep_param_keys(param_name, keys): + util.validate_allowed_keys(keys, ['counts', 'displs', 'types', 'comm'], 'WBUFFER', + param_name) + + def declare(self): + return f'{compiler.OMPI_F08_IGNORE_TKR_TYPE}, INTENT(IN) :: {self.name}' + + def c_parameter(self): + if compiler.HAVE_TS: + return f'CFI_cdesc_t *{self.name}' + else: + return f'char *{self.name}' + + def c_declare_tmp(self): + lines = [f'MPI_Datatype *{self.dep_params["types"].tmp_name} = NULL;'] + if compiler.HAVE_TS: + lines.append(f'char *{self.tmp_name} = {self.name}->base_addr;') + return '\n'.join(lines) + + def c_prepare(self): + first_part = super().c_prepare() + datatypes = self.dep_params['types'] + comm = self.dep_params['comm'] + counts = self.dep_params['counts'] + displs = self.dep_params['displs'] + name = self.tmp_name if compiler.HAVE_TS else self.name + second_part = util.prepare_text(f""" + if (!OMPI_IS_FORTRAN_IN_PLACE({name})) {{ + {datatypes.tmp_name} = malloc({comm.size} * sizeof(MPI_Datatype)); + OMPI_ARRAY_FINT_2_INT({counts.name}, {comm.size}); + OMPI_ARRAY_FINT_2_INT({displs.name}, {comm.size}); + for (int i = 0; i < size; ++i) {{ + {datatypes.tmp_name}[i] = PMPI_Type_f2c({datatypes.name}[i]); + }} + }}""") + return '\n'.join([first_part, second_part]) + + def c_argument(self): + name = self.tmp_name if compiler.HAVE_TS else self.name + return f'OMPI_F2C_BOTTOM(OMPI_F2C_IN_PLACE({name}))' + + def c_post(self): + datatypes = self.dep_params['types'] + counts = self.dep_params['counts'] + displs = self.dep_params['displs'] + name = self.tmp_name if compiler.HAVE_TS else self.name + return util.prepare_text(f""" + if (!OMPI_IS_FORTRAN_IN_PLACE({name})) {{ + free({datatypes.tmp_name}); + OMPI_ARRAY_FINT_2_INT_CLEANUP({counts.name}); + OMPI_ARRAY_FINT_2_INT_CLEANUP({displs.name}); + }}""") + + +@FortranType.add('WBUFFER_OUT') +class WBufferType(WBufferBase): + """Variable buffer receive type, used with MPI_*w() functions.""" + @staticmethod + def validate_dep_param_keys(param_name, keys): + util.validate_allowed_keys(keys, ['comm', 'counts', 'displs', 'types'], 'WBUFFER_OUT', param_name) + + def declare(self): + return f'{compiler.OMPI_F08_IGNORE_TKR_TYPE} :: {self.name}' + + def c_parameter(self): + if compiler.HAVE_TS: + return f'CFI_cdesc_t *{self.name}' + else: + return f'char *{self.name}' + + def c_declare_tmp(self): + lines = [f'MPI_Datatype *{self.dep_params["types"].tmp_name} = NULL;'] + if compiler.HAVE_TS: + lines.append(f'char *{self.tmp_name} = {self.name}->base_addr;') + return '\n'.join(lines) + + def c_prepare(self): + first_part = super().c_prepare() + comm = self.dep_params['comm'] + datatype = self.dep_params['types'] + counts = self.dep_params['counts'] + displs = self.dep_params['displs'] + second_part = util.prepare_text(f""" + {datatype.tmp_name} = malloc({comm.size} * sizeof(MPI_Datatype)); + for (int i = 0; i < {comm.size}; ++i) {{ + {datatype.tmp_name}[i] = PMPI_Type_f2c({datatype.name}[i]); + }} + OMPI_ARRAY_FINT_2_INT({counts.name}, {comm.size}); + OMPI_ARRAY_FINT_2_INT({displs.name}, {comm.size});""") + return '\n'.join([first_part, second_part]) + + def c_argument(self): + name = self.tmp_name if compiler.HAVE_TS else self.name + return f'OMPI_F2C_BOTTOM({name})' + + def c_post(self): + datatypes = self.dep_params['types'] + counts = self.dep_params['counts'] + displs = self.dep_params['displs'] + return util.prepare_text(f""" + free({datatypes.tmp_name}); + OMPI_ARRAY_FINT_2_INT_CLEANUP({counts.name}); + OMPI_ARRAY_FINT_2_INT_CLEANUP({displs.name});""") + + +@FortranType.add('COUNT') +class CountType(FortranType): + def declare(self): + if self.bigcount: + return f'INTEGER(KIND=MPI_COUNT_KIND), INTENT(IN) :: {self.name}' + else: + return f'INTEGER, INTENT(IN) :: {self.name}' + + def use(self): + return [('mpi_f08_types', 'MPI_COUNT_KIND')] + + def c_parameter(self): + type_ = 'MPI_Count' if self.bigcount else 'MPI_Fint' + return f'{type_} *{self.name}' + + def c_argument(self): + arg = self.tmp_name if compiler.HAVE_TS else f'*{self.name}' + return arg if self.bigcount else f'OMPI_FINT_2_INT({arg})' + + +@FortranType.add('DATATYPE') +class DatatypeType(FortranType): + def declare(self): + return f'TYPE(MPI_Datatype), INTENT(IN) :: {self.name}' + + def declare_cbinding_fortran(self): + return f'INTEGER, INTENT(IN) :: {self.name}' + + def argument(self): + return f'{self.name}%MPI_VAL' + + def use(self): + return [('mpi_f08_types', 'MPI_Datatype')] + + def c_parameter(self): + return f'MPI_Fint *{self.name}' + + if compiler.HAVE_TS: + def c_prepare(self): + # Preparation code is done by the BUFFER type + return '' + + def c_argument(self): + return self.tmp_name2 + else: + def c_prepare(self): + return f'MPI_Datatype {self.tmp_name} = PMPI_Type_f2c(*{self.name});' + + def c_argument(self): + return self.tmp_name + + +@FortranType.add('DATATYPE_ARRAY') +class DatatypeArrayType(FortranType): + def declare(self): + return f'TYPE(MPI_Datatype), INTENT(IN) :: {self.name}(*)' + + def use(self): + return [('mpi_f08_types', 'MPI_Datatype')] + + def c_parameter(self): + return f'MPI_Fint *{self.name}' + + # Initialization is taken care of by the WBUFFER type + + def c_argument(self): + return self.tmp_name + + +@FortranType.add('INT') +class IntType(FortranType): + def declare(self): + return f'INTEGER, INTENT(IN) :: {self.name}' + + def c_parameter(self): + return f'MPI_Fint *{self.name}' + + def c_argument(self): + return f'OMPI_FINT_2_INT(*{self.name})' + + +@FortranType.add('RANK') +class RankType(IntType): + pass + + +@FortranType.add('TAG') +class TagType(IntType): + pass + + +@FortranType.add('INDEX_OUT') +class IndexOutType(IntType): + def declare(self): + return f'INTEGER, INTENT(OUT) :: {self.name}' + + def c_declare_tmp(self): + return f'int {self.tmp_name};' + + def c_shortcut_code(self): + return f'*{self.name} = OMPI_INT_2_FINT(MPI_UNDEFINED);' + + def c_argument(self): + return f'&{self.tmp_name}' + + def c_post(self): + return util.prepare_text(f""" + if (MPI_SUCCESS == {consts.C_ERROR_TMP_NAME} && MPI_UNDEFINED != {self.tmp_name}) {{ + {self.tmp_name} += 1; + *{self.name} = OMPI_INT_2_FINT({self.tmp_name}); + }}""") + + +@FortranType.add('LOGICAL_OUT') +class LogicalOutType(IntType): + """Logical type. + + NOTE: Since the logical type causes difficulties when passed to C code, + this code uses a temporary integer in Fortran to pass to the C code. On + completion the logical type is set based on C's true/false rules. + """ + + def declare(self): + return f'LOGICAL, INTENT(OUT) :: {self.name}' + + def declare_tmp(self): + return f'INTEGER :: {self.tmp_name} = 0' + + def declare_cbinding_fortran(self): + return f'INTEGER, INTENT(OUT) :: {self.name}' + + def argument(self): + return self.tmp_name + + def post(self): + return f'{self.name} = {self.tmp_name} /= 0' + + def c_parameter(self): + return f'MPI_Fint *{self.name}' + + def c_declare_tmp(self): + return f'int {self.tmp_name};' + + def c_shortcut_code(self): + return f'*{self.name} = OMPI_INT_2_FINT(1);' + + def c_argument(self): + return f'&{self.tmp_name}' + + def c_post(self): + return f'*{self.name} = OMPI_INT_2_FINT({self.tmp_name});' + + +# List of functions that need a communicator 'size' temporary +FUNCTIONS_NEEDING_COMM_SIZE_TEMP = [ + 'alltoallv', + 'alltoallw', +] + + +@FortranType.add('COMM') +class CommType(FortranType): + def declare(self): + return f'TYPE(MPI_Comm), INTENT(IN) :: {self.name}' + + def declare_cbinding_fortran(self): + return f'INTEGER, INTENT(IN) :: {self.name}' + + def argument(self): + return f'{self.name}%MPI_VAL' + + def use(self): + return [('mpi_f08_types', 'MPI_Comm')] + + def c_parameter(self): + return f'MPI_Fint *{self.name}' + + def c_declare_tmp(self): + code = [f'MPI_Comm {self.tmp_name} = PMPI_Comm_f2c(*{self.name});'] + if self.fn_name in FUNCTIONS_NEEDING_COMM_SIZE_TEMP: + code.append(f'int {self.size} = OMPI_COMM_IS_INTER({self.tmp_name}) ? ' + f'ompi_comm_remote_size({self.tmp_name}) : ompi_comm_size({self.tmp_name});') + return '\n'.join(code) + + def c_argument(self): + return self.tmp_name + + @property + def size(self): + """Size property accessible by all dependent types.""" + return 'size' + + +@FortranType.add('STATUS') +class StatusType(FortranType): + def declare(self): + return f'TYPE(MPI_Status), INTENT(OUT) :: {self.name}' + + def use(self): + return [('mpi_f08_types', 'MPI_Status')] + + def c_parameter(self): + # TODO: Is this correct? (I've listed it as TYPE(MPI_Status) in the binding) + return f'MPI_Fint *{self.name}' + + def c_shortcut_code(self): + return f'PMPI_Status_c2f(&ompi_status_empty, {self.name});' + + def c_declare_tmp(self): + return util.prepare_text(f""" + OMPI_FORTRAN_STATUS_DECLARATION({self.tmp_name}, {self.tmp_name2}); + OMPI_FORTRAN_STATUS_SET_POINTER({self.tmp_name}, {self.tmp_name2}, {self.name}); + """) + + def c_argument(self): + return self.tmp_name + + def c_post(self): + return f'OMPI_FORTRAN_STATUS_RETURN({self.tmp_name}, {self.tmp_name2}, {self.name}, {consts.C_ERROR_TMP_NAME});' + + +@FortranType.add('SHORTCUT_COUNT') +class ShortcutCountType(FortranType): + """Shortcut count type. + + This type is an integer that, when 0, can be used to shortcut a call to the + underyling C binding. Other types may implement a `c_shortcut` method that + will return code to execute upon a shortcut operation. + + The shortcut conditional is placed right after c temporary declarations but + before the c prepare code. + """ + + def declare(self): + return f'INTEGER, INTENT(IN) :: {self.name}' + + def c_parameter(self): + return f'MPI_Fint *{self.name}' + + def c_shortcut_condition(self): + return f'OPAL_UNLIKELY(0 == OMPI_FINT_2_INT(*{self.name}))' + + def c_argument(self): + return f'OMPI_FINT_2_INT(*{self.name})' + + +@FortranType.add('REQUEST') +class RequestType(FortranType): + def declare(self): + return f'TYPE(MPI_Request), INTENT(OUT) :: {self.name}' + + def declare_cbinding_fortran(self): + return f'INTEGER, INTENT(OUT) :: {self.name}' + + def argument(self): + return f'{self.name}%MPI_VAL' + + def use(self): + return [('mpi_f08_types', 'MPI_Request')] + + def c_parameter(self): + return f'MPI_Fint *{self.name}' + + def c_declare_tmp(self): + return f'MPI_Request {self.tmp_name};' + + def c_argument(self): + return f'&{self.tmp_name}' + + def c_post(self): + return util.prepare_text(f""" + if (MPI_SUCCESS == {consts.C_ERROR_TMP_NAME}) {{ + *{self.name} = PMPI_Request_c2f({self.tmp_name}); + }}""") + + +def allocate_array(name, malloc_expr, fn_api_name): + """Generate code for allocating an array and checking the result.""" + return util.prepare_text(f""" + {name} = malloc({malloc_expr}); + if (NULL == {name}) {{ + {consts.C_ERROR_TMP_NAME} = OMPI_ERRHANDLER_NOHANDLE_INVOKE(MPI_ERR_NO_MEM, "{fn_api_name}"); + *{consts.C_ERROR_NAME} = OMPI_INT_2_FINT({consts.C_ERROR_TMP_NAME}); + return; + }}""") + + +@FortranType.add('REQUEST_ARRAY') +class RequestArrayType(FortranType): + @staticmethod + def validate_dep_param_keys(param_name, keys): + util.validate_allowed_keys(keys, ['count'], 'REQUEST_ARRAY', param_name) + + def declare(self): + return f'TYPE(MPI_Request), INTENT(INOUT) :: {self.name}({self.dep_params["count"].name})' + + def declare_cbinding_fortran(self): + return f'INTEGER, INTENT(INOUT) :: {self.name}({self.dep_params["count"].name})' + + def argument(self): + return f'{self.name}(:)%MPI_VAL' + + def use(self): + return [('mpi_f08_types', 'MPI_Request')] + + def c_parameter(self): + return f'MPI_Fint *{self.name}' + + def c_declare_tmp(self): + return f'MPI_Request *{self.tmp_name};' + + def c_prepare(self): + tmp_name = self.tmp_name + code = [allocate_array(tmp_name, + f'{self.dep_params["count"].c_argument()} * sizeof(MPI_Request)', + self.fn_api_name)] + i = self.tmp_counter() + code.append(util.prepare_text(f""" + for (int {i} = 0; {i} < {self.dep_params["count"].c_argument()}; ++{i}) {{ + {tmp_name}[{i}] = PMPI_Request_f2c({self.name}[{i}]); + }}""")) + return '\n'.join(code) + + def c_argument(self): + return self.tmp_name + + def c_post(self): + i = self.tmp_counter() + return util.prepare_text(f""" + if (MPI_SUCCESS == {consts.C_ERROR_TMP_NAME}) {{ + for (int {i} = 0; {i} < {self.dep_params["count"].c_argument()}; ++{i}) {{ + {self.name}[{i}] = {self.tmp_name}[{i}]->req_f_to_c_index; + }} + }} + free({self.tmp_name});""") + + +@FortranType.add('STATUS_ARRAY') +class StatusArrayType(FortranType): + @staticmethod + def validate_dep_param_keys(param_name, keys): + util.validate_allowed_keys(keys, ['count'], 'STATUS_ARRAY', param_name) + + def declare(self): + return f'TYPE(MPI_Status), INTENT(OUT) :: {self.name}(*)' + + def use(self): + return [('mpi_f08_types', 'MPI_Status')] + + def c_parameter(self): + return f'MPI_Fint *{self.name}' + + def c_declare_tmp(self): + return f'MPI_Status *{self.tmp_name};' + + def c_prepare(self): + return allocate_array(self.tmp_name, + f'{self.dep_params["count"].c_argument()} * sizeof(MPI_Status)', + self.fn_api_name) + + def c_argument(self): + return self.tmp_name + + def c_post(self): + i = self.tmp_counter() + return util.prepare_text(f""" + if (MPI_SUCCESS == {consts.C_ERROR_TMP_NAME}) {{ + for (int {i} = 0; {i} < {self.dep_params["count"].c_argument()}; ++{i}) {{ + if (!OMPI_IS_FORTRAN_STATUSES_IGNORE({self.name}) && + !OMPI_IS_FORTRAN_STATUS_IGNORE(&{self.name}[{i}])) {{ + PMPI_Status_c2f(&{self.tmp_name}[{i}], &{self.name}[{i} * (sizeof(MPI_Status) / sizeof(int))]); + }} + }} + }} + free({self.tmp_name});""") + + +@FortranType.add('INT_ARRAY') +class IntArray(FortranType): + """Integer array as used for MPI_*v() variable length functions.""" + + @staticmethod + def validate_dep_param_keys(param_name, keys): + util.validate_allowed_keys(keys, ['comm'], 'INT_ARRAY', param_name) + + def declare(self): + return f'INTEGER, INTENT(IN) :: {self.name}(*)' + + def c_parameter(self): + return f'MPI_Fint *{self.name}' + + def c_declare_tmp(self): + return f'OMPI_ARRAY_NAME_DECL({self.name});' + + # NOTE: Most of this code is initialized and freed in the W/VBUFFER code + + def c_argument(self): + return f'OMPI_ARRAY_NAME_CONVERT({self.name})' + + +@FortranType.add('COUNT_ARRAY') +class CountArray(IntArray): + """Array of MPI_Count or int.""" + + def declare(self): + kind = '(KIND=MPI_COUNT_KIND)' if self.bigcount else '' + return f'INTEGER{kind}, INTENT(IN) :: {self.name}(*)' + + def use(self): + if self.bigcount: + return [('mpi_f08_types', 'MPI_COUNT_KIND')] + return [] + + def c_parameter(self): + count_type = 'MPI_Count' if self.bigcount else 'MPI_Fint' + return f'{count_type} *{self.name}' + + +@FortranType.add('DISPL_ARRAY') +class DisplArray(IntArray): + """Array of MPI_Aint or int.""" + + def declare(self): + kind = '(KIND=MPI_ADDRESS_KIND)' if self.bigcount else '' + return f'INTEGER{kind}, INTENT(IN) :: {self.name}(*)' + + def use(self): + if self.bigcount: + return [('mpi_f08_types', 'MPI_ADDRESS_KIND')] + return [] + + def c_parameter(self): + count_type = 'MPI_Aint' if self.bigcount else 'MPI_Fint' + return f'{count_type} *{self.name}' diff --git a/ompi/mpi/bindings/ompi_bindings/util.py b/ompi/mpi/bindings/ompi_bindings/util.py new file mode 100644 index 00000000000..45ef34ea497 --- /dev/null +++ b/ompi/mpi/bindings/ompi_bindings/util.py @@ -0,0 +1,131 @@ +# Copyright (c) 2024 Triad National Security, LLC. All rights +# reserved. +# +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +"""Utility code for OMPI binding generation.""" +import textwrap + + +class OutputFile: + """Output file of script.""" + + def __init__(self, fp): + self.fp = fp + + def dump(self, *pargs, **kwargs): + print(*pargs, **kwargs, file=self.fp) + + +def prepare_text(text): + """Prepare text to be output, removing extra lines and whitespace.""" + text = textwrap.dedent(text) + lines = text.split('\n') + out_lines = [] + new_lines = 0 + for line in lines: + line = line.rstrip() + # Only allow one blank line + if not line: + new_lines += 1 + else: + new_lines = 0 + if new_lines > 1: + continue + out_lines.append(line) + return '\n'.join(line for line in lines if line.strip()) + + +class BindingError(Exception): + """Thrown when a binding error is encountered.""" + + +def validate_allowed_keys(keys, req_keys, type_name, param_name): + """Validate allowed keys for a type, raising an error on failure.""" + missing_keys = [key for key in req_keys if key not in keys] + invalid_keys = [key for key in keys if key not in req_keys] + init_message = f'Param {param_name} with type {type_name}' + if missing_keys and invalid_keys: + raise BindingError(f'{init_message} has missing keys ({missing_keys}) and invalid keys ({invalid_keys})') + elif missing_keys: + raise BindingError(f'{init_message} has missing keys: {missing_keys}') + elif invalid_keys: + raise BindingError(f'{init_message} has invalid keys: {invalid_keys}') + + +def ext_api_func_name(fn_name, bigcount=False): + """Produce the external MPI API function name.""" + suffix = '_c' if bigcount else '' + return f'MPI_{fn_name.capitalize()}{suffix}' + + +def ext_api_func_name_profile(fn_name, bigcount=False): + """Produce the external PMPI API function name.""" + return f'P{ext_api_func_name(fn_name, bigcount)}' + + +def fortran_f08_name(fn_name, bigcount=False): + """Produce the final f08 name from base_name.""" + suffix = '_c' if bigcount else '' + return f'MPI_{fn_name.capitalize()}_f08{suffix}' + + +def break_param_lines_fortran(start, params, end): + """Break paramters for a fortran call onto multiple lines. + + This is often necessary to avoid going over the max line length of 132 + characters. + """ + assert len(params) > 1, 'expected more than one parameter' + indent = len(start) * ' ' + lines = [f'{start}{params[0]},'] + for param in params[1:-1]: + lines.append(f'{indent}{param},') + last_line = f'{indent}{params[-1]}{end}' + max_len = max(len(line) for line in lines) + max_len = max(max_len, len(last_line)) + result_lines = [] + for line in lines: + spaces = (max_len - len(line) + 1) * ' ' + result_lines.append(f'{line}{spaces}&') + result_lines.append(last_line) + return result_lines + + +def indent_lines(lines, tab, start=0): + """Crude pretty-printing function.""" + new_lines = [] + indent_count = start + for line in lines: + # Closing bracket + if '}' in line: + indent_count -= 1 + + prefix = indent_count * tab + new_lines.append(f'{prefix}{line}') + + # Opening bracket + if '{' in line: + indent_count += 1 + return new_lines + + +def mpi_fn_name_from_base_fn_name(name): + """Convert from a base name to the standard 'MPI_*' name.""" + return f'MPI_{name.capitalize()}' + + +def abi_internal_name(extname): + """Convert from the ABI external name to an internal name. + + Used to avoid conflicts with existing MPI names. + """ + return f'{extname}_ABI_INTERNAL' + + +def fortran_prototype_has_bigcount(prototype): + """Should this prototype have a bigcount version?""" + return any(param.type_name in ('COUNT', 'COUNT_ARRAY') for param in prototype.parameters) diff --git a/ompi/mpi/c/Makefile.am b/ompi/mpi/c/Makefile.am index 6fc08bc75eb..e1bd96163b3 100644 --- a/ompi/mpi/c/Makefile.am +++ b/ompi/mpi/c/Makefile.am @@ -43,6 +43,8 @@ headers = bindings.h prototype_sources = \ abort.c.in \ + alltoallv.c.in \ + alltoallw.c.in \ send.c.in \ isend.c.in \ recv.c.in \ @@ -67,9 +69,8 @@ prototype_sources = \ get_processor_name.c.in \ barrier.c.in -# Include script and template files in case someone wants to update the -# template files -EXTRA_DIST = generate_bindings.py $(prototype_sources) +# Include template files in case someone wants to update them +EXTRA_DIST = $(prototype_sources) # attr_fn.c contains attribute manipulation functions which do not # profiling implications, and so are always built. @@ -107,10 +108,10 @@ interface_profile_sources = \ alltoall.c \ ialltoall.c \ alltoall_init.c \ - alltoallv.c \ + ompi_alltoallv.c \ ialltoallv.c \ alltoallv_init.c \ - alltoallw.c \ + ompi_alltoallw.c \ ialltoallw.c \ alltoallw_init.c \ attr_delete.c \ @@ -542,8 +543,15 @@ libmpi_c_noprofile_la_CPPFLAGS = -DOMPI_BUILD_MPI_PROFILING=0 # ABI generation rules if OMPI_GENERATE_BINDINGS -ompi_%.c: %.c.in generate_bindings.py - $(PYTHON) $(srcdir)/generate_bindings.py source ompi $< > $@ +ompi_%.c: %.c.in + $(PYTHON) $(top_srcdir)/ompi/mpi/bindings/bindings.py \ + --builddir $(abs_top_builddir) \ + --srcdir $(abs_top_srcdir) \ + --output $@ \ + c \ + source \ + ompi \ + $< distclean-local: -rm -rf ompi_*.c diff --git a/ompi/mpi/c/alltoallv.c b/ompi/mpi/c/alltoallv.c.in similarity index 91% rename from ompi/mpi/c/alltoallv.c rename to ompi/mpi/c/alltoallv.c.in index 4106d96d249..168b238ee36 100644 --- a/ompi/mpi/c/alltoallv.c +++ b/ompi/mpi/c/alltoallv.c.in @@ -34,20 +34,10 @@ #include "ompi/memchecker.h" #include "ompi/runtime/ompi_spc.h" -#if OMPI_BUILD_MPI_PROFILING -#if OPAL_HAVE_WEAK_SYMBOLS -#pragma weak MPI_Alltoallv = PMPI_Alltoallv -#endif -#define MPI_Alltoallv PMPI_Alltoallv -#endif - -static const char FUNC_NAME[] = "MPI_Alltoallv"; - - -int MPI_Alltoallv(const void *sendbuf, const int sendcounts[], - const int sdispls[], MPI_Datatype sendtype, - void *recvbuf, const int recvcounts[], const int rdispls[], - MPI_Datatype recvtype, MPI_Comm comm) +PROTOTYPE ERROR_CLASS alltoallv(BUFFER sendbuf, COUNT_ARRAY sendcounts, + DISPL_ARRAY sdispls, DATATYPE sendtype, + BUFFER_OUT recvbuf, COUNT_ARRAY recvcounts, DISPL_ARRAY rdispls, + DATATYPE recvtype, COMM comm) { int i, size, err; diff --git a/ompi/mpi/c/alltoallw.c b/ompi/mpi/c/alltoallw.c.in similarity index 90% rename from ompi/mpi/c/alltoallw.c rename to ompi/mpi/c/alltoallw.c.in index e8850326df4..c1fe52de8bd 100644 --- a/ompi/mpi/c/alltoallw.c +++ b/ompi/mpi/c/alltoallw.c.in @@ -34,20 +34,10 @@ #include "ompi/memchecker.h" #include "ompi/runtime/ompi_spc.h" -#if OMPI_BUILD_MPI_PROFILING -#if OPAL_HAVE_WEAK_SYMBOLS -#pragma weak MPI_Alltoallw = PMPI_Alltoallw -#endif -#define MPI_Alltoallw PMPI_Alltoallw -#endif - -static const char FUNC_NAME[] = "MPI_Alltoallw"; - - -int MPI_Alltoallw(const void *sendbuf, const int sendcounts[], - const int sdispls[], const MPI_Datatype sendtypes[], - void *recvbuf, const int recvcounts[], const int rdispls[], - const MPI_Datatype recvtypes[], MPI_Comm comm) +PROTOTYPE ERROR_CLASS alltoallw(BUFFER sendbuf, COUNT_ARRAY sendcounts, + DISPL_ARRAY sdispls, DATATYPE_ARRAY sendtypes, + BUFFER_OUT recvbuf, COUNT_ARRAY recvcounts, DISPL_ARRAY rdispls, + DATATYPE_ARRAY recvtypes, COMM comm) { int i, size, err; diff --git a/ompi/mpi/c/generate_bindings.py b/ompi/mpi/c/generate_bindings.py deleted file mode 100755 index f40986ff981..00000000000 --- a/ompi/mpi/c/generate_bindings.py +++ /dev/null @@ -1,1278 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2023 Triad National Security, LLC. All rights reserved. -# Copyright (c) 2023 Research Organization for Information Science -# and Technology (RIST). All rights reserved. -# $COPYRIGHT$ -# -# Additional copyrights may follow -# -# $HEADERS$ -# -# -"""MPI Standard ABI Generation. - -TEMPLATE SOURCE FILE ASSUMPTIONS: -* Only one function per file -* Nothing (other than blank lines) after closing '}' -* Function prototype is preceded by PROTOTYPE -* All types in the function prototype are converted to one-word capital types - as defined here (to be later converted to ompi or standard ABI types) -* Functions requiring a bigcount implementation should have type COUNT in - place of MPI_Count or int for each count parameter. Bigcount functions will - be generated automatically for any function that includes a COUNT type. -""" -from abc import ABC, abstractmethod -import argparse -import re -import sys -import os - -# C type: const int -ERROR_CLASSES = [ - 'MPI_SUCCESS', - 'MPI_ERR_BUFFER', - 'MPI_ERR_COUNT', - 'MPI_ERR_TYPE', - 'MPI_ERR_TAG', - 'MPI_ERR_COMM', - 'MPI_ERR_RANK', - 'MPI_ERR_REQUEST', - 'MPI_ERR_ROOT', - 'MPI_ERR_GROUP', - 'MPI_ERR_OP', - 'MPI_ERR_TOPOLOGY', - 'MPI_ERR_DIMS', - 'MPI_ERR_ARG', - 'MPI_ERR_UNKNOWN', - 'MPI_ERR_TRUNCATE', - 'MPI_ERR_OTHER', - 'MPI_ERR_INTERN', - 'MPI_ERR_PENDING', - 'MPI_ERR_IN_STATUS', - 'MPI_ERR_ACCESS', - 'MPI_ERR_AMODE', - 'MPI_ERR_ASSERT', - 'MPI_ERR_BAD_FILE', - 'MPI_ERR_BASE', - 'MPI_ERR_CONVERSION', - 'MPI_ERR_DISP', - 'MPI_ERR_DUP_DATAREP', - 'MPI_ERR_FILE_EXISTS', - 'MPI_ERR_FILE_IN_USE', - 'MPI_ERR_FILE', - 'MPI_ERR_INFO_KEY', - 'MPI_ERR_INFO_NOKEY', - 'MPI_ERR_INFO_VALUE', - 'MPI_ERR_INFO', - 'MPI_ERR_IO', - 'MPI_ERR_KEYVAL', - 'MPI_ERR_LOCKTYPE', - 'MPI_ERR_NAME', - 'MPI_ERR_NO_MEM', - 'MPI_ERR_NOT_SAME', - 'MPI_ERR_NO_SPACE', - 'MPI_ERR_NO_SUCH_FILE', - 'MPI_ERR_PORT', - 'MPI_ERR_PROC_ABORTED', - 'MPI_ERR_QUOTA', - 'MPI_ERR_READ_ONLY', - 'MPI_ERR_RMA_ATTACH', - 'MPI_ERR_RMA_CONFLICT', - 'MPI_ERR_RMA_RANGE', - 'MPI_ERR_RMA_SHARED', - 'MPI_ERR_RMA_SYNC', - 'MPI_ERR_RMA_FLAVOR', - 'MPI_ERR_SERVICE', - 'MPI_ERR_SESSION', - 'MPI_ERR_SIZE', - 'MPI_ERR_SPAWN', - 'MPI_ERR_UNSUPPORTED_DATAREP', - 'MPI_ERR_UNSUPPORTED_OPERATION', - 'MPI_ERR_WIN', - 'MPI_T_ERR_CANNOT_INIT', - 'MPI_T_ERR_NOT_INITIALIZED', - 'MPI_T_ERR_MEMORY', - 'MPI_T_ERR_INVALID', - 'MPI_T_ERR_INVALID_INDEX', - 'MPI_T_ERR_INVALID_ITEM', - 'MPI_T_ERR_INVALID_SESSION', - 'MPI_T_ERR_INVALID_HANDLE', - 'MPI_T_ERR_INVALID_NAME', - 'MPI_T_ERR_OUT_OF_HANDLES', - 'MPI_T_ERR_OUT_OF_SESSIONS', - 'MPI_T_ERR_CVAR_SET_NOT_NOW', - 'MPI_T_ERR_CVAR_SET_NEVER', - 'MPI_T_ERR_PVAR_NO_WRITE', - 'MPI_T_ERR_PVAR_NO_STARTSTOP', - 'MPI_T_ERR_PVAR_NO_ATOMIC', - 'MPI_ERR_LASTCODE', -] - -PREDEFINED_DATATYPES = [ - 'MPI_CHAR', - 'MPI_SHORT', - 'MPI_INT', - 'MPI_LONG', - 'MPI_LONG_LONG_INT', - 'MPI_LONG_LONG', - 'MPI_SIGNED_CHAR', - 'MPI_UNSIGNED_CHAR', - 'MPI_UNSIGNED_SHORT', - 'MPI_UNSIGNED', - 'MPI_UNSIGNED_LONG', - 'MPI_UNSIGNED_LONG_LONG', - 'MPI_FLOAT', - 'MPI_DOUBLE', - 'MPI_LONG_DOUBLE', - 'MPI_WCHAR', - 'MPI_C_BOOL', - 'MPI_INT8_T', - 'MPI_INT16_T', - 'MPI_INT32_T', - 'MPI_INT64_T', - 'MPI_UINT8_T', - 'MPI_UINT16_T', - 'MPI_UINT32_T', - 'MPI_UINT64_T', - 'MPI_AINT', - 'MPI_COUNT', - 'MPI_OFFSET', - 'MPI_C_COMPLEX', - 'MPI_C_FLOAT_COMPLEX', - 'MPI_C_DOUBLE_COMPLEX', - 'MPI_C_LONG_DOUBLE_COMPLEX', - 'MPI_BYTE', - 'MPI_PACKED', - 'MPI_CXX_BOOL', - 'MPI_CXX_FLOAT_COMPLEX', - 'MPI_CXX_DOUBLE_COMPLEX', - 'MPI_CXX_LONG_DOUBLE_COMPLEX', - 'MPI_FLOAT_INT', - 'MPI_DOUBLE_INT', - 'MPI_LONG_INT', - 'MPI_2INT', - 'MPI_SHORT_INT', - 'MPI_LONG_DOUBLE_INT', -] - -# C type: MPI_Comm -RESERVED_COMMUNICATORS = [ - 'MPI_COMM_NULL', - 'MPI_COMM_WORLD', - 'MPI_COMM_SELF', -] - -COMMUNICATOR_SPLIT_TYPES = [ - 'MPI_COMM_TYPE_SHARED', - 'MPI_COMM_TYPE_HW_UNGUIDED', - 'MPI_COMM_TYPE_HW_GUIDED', -] - -RESERVED_WINDOWS = [ - 'MPI_WIN_NULL', -] - -RESERVED_REQUESTS = [ - 'MPI_REQUEST_NULL', -] - -RESERVED_INFOS = [ - 'MPI_INFO_ENV', - 'MPI_INFO_NULL', -] - -RESERVED_FILES = [ - 'MPI_FILE_NULL', -] - -IGNORED_STATUS_HANDLES = [ - 'MPI_STATUSES_IGNORE', - 'MPI_STATUS_IGNORE', -] - -COLLECTIVE_OPERATIONS = [ - 'MPI_MAX', - 'MPI_MIN', - 'MPI_SUM', - 'MPI_PROD', - 'MPI_MAXLOC', - 'MPI_MINLOC', - 'MPI_BAND', - 'MPI_BOR', - 'MPI_BXOR', - 'MPI_LAND', - 'MPI_LOR', - 'MPI_LXOR', - 'MPI_REPLACE', - 'MPI_NO_OP', -] - -VARIOUS_CONSTANTS = { - # Just setting this to the same as ompi ABI for right now, but will need to - # match the standard ABI value when defined - 'MPI_MAX_LIBRARY_VERSION_STRING': 256, - 'MPI_MAX_PROCESSOR_NAME': 256, -} - -# Types - -C_OPAQUE_TYPES = { - 'MPI_Aint': 'intptr_t', - 'MPI_Offset': 'int64_t', - 'MPI_Count': 'size_t', - # The below type needs to be set externally depending on Fortran compiler - 'MPI_Fint': 'int64_t', -} - -C_HANDLES = [ - 'MPI_Comm', - 'MPI_Datatype', - 'MPI_Errhandler', - 'MPI_File', - 'MPI_Group', - 'MPI_Info', - 'MPI_Message', - 'MPI_Op', - 'MPI_Request', - 'MPI_Session', - 'MPI_Win', -] - - -class ConvertFuncs: - """Names of conversion functions (between standard ABI and OMPI ABI).""" - - ERROR_CLASS = 'ompi_convert_intern_error_abi_error' - COMM = 'ompi_convert_abi_comm_intern_comm' - DATATYPE = 'ompi_convert_abi_datatype_intern_datatype' - REQUEST = 'ompi_convert_abi_request_intern_request' - STATUS = 'ompi_convert_intern_status_abi_status' - OP = 'ompi_convert_abi_op_intern_op' - WIN = 'ompi_convert_abi_win_intern_win' - INFO = 'ompi_convert_abi_info_intern_info' - FILE = 'ompi_convert_abi_file_intern_file' - - -class ConvertOMPIToStandard: - """Generated function for converting from OMPI to standard ABI.""" - - COMM = 'ompi_convert_comm_ompi_to_standard' - - -# Inline function attributes -INLINE_ATTRS = '__opal_attribute_always_inline__ static inline' - - -def mpi_fn_name_from_base_fn_name(name): - """Convert from a base name to the standard 'MPI_*' name.""" - return f'MPI_{name.capitalize()}' - - -def abi_internal_name(extname): - """Convert from the ABI external name to an internal name. - - Used to avoid conflicts with existing MPI names. - """ - return f'{extname}_ABI_INTERNAL' - - -class ABIHeaderBuilder: - """ABI header builder code.""" - - def __init__(self, prototypes, external=False, file=sys.stdout): - self.file = file - self.external = external - - if external: - mangle_name = lambda name: name - else: - mangle_name = abi_internal_name - - # Build up the list of standard ABI signatures - signatures = [] - for prototype in prototypes: - base_name = mpi_fn_name_from_base_fn_name(prototype.name) - signatures.append(prototype.signature('standard', base_name, - mangle_name=mangle_name)) - # Profiling prototype - signatures.append(prototype.signature('standard', f'P{base_name}', - mangle_name=mangle_name)) - if prototype.need_bigcount: - signatures.append(prototype.signature('standard', f'{base_name}_c', - count_type='MPI_Count', - mangle_name=mangle_name)) - # Profiling prototype - signatures.append(prototype.signature('standard', f'P{base_name}_c', - count_type='MPI_Count', - mangle_name=mangle_name)) - self.signatures = signatures - - def mangle_name(self, extname): - """Mangle names, depending on whether building external or internal header.""" - if self.external: - return extname - return abi_internal_name(extname) - - def dump(self, *pargs, **kwargs): - print(*pargs, **kwargs, file=self.file) - - def dump_lines(self, lines): - lines = indent_lines(lines, 4 * ' ', start=1) - for line in lines: - self.dump(line) - - def generate_error_convert_fn(self): - self.dump(f'{INLINE_ATTRS} int {ConvertFuncs.ERROR_CLASS}(int error_class)') - self.dump('{') - lines = [] - lines.append('switch (error_class) {') - for error in ERROR_CLASSES: - lines.append(f'case {self.mangle_name(error)}:') - lines.append(f'return {error};') - lines.append('default:') - lines.append('return error_class;') - lines.append('}') - self.dump_lines(lines) - self.dump('}') - - def generic_convert(self, fn_name, param_name, type_, value_names): - intern_type = self.mangle_name(type_) - self.dump(f'{INLINE_ATTRS} {type_} {fn_name}({intern_type} {param_name})') - self.dump('{') - lines = [] - for i, value_name in enumerate(value_names): - intern_name = self.mangle_name(value_name) - if i == 0: - lines.append('if (%s == %s) {' % (intern_name, param_name)) - else: - lines.append('} else if (%s == %s) {' % (intern_name, param_name)) - lines.append(f'return {value_name};') - lines.append('}') - lines.append(f'return ({type_}) {param_name};') - self.dump_lines(lines) - self.dump('}') - - def generic_convert_reverse(self, fn_name, param_name, type_, value_names): - intern_type = self.mangle_name(type_) - self.dump(f'{INLINE_ATTRS} {intern_type} {fn_name}({type_} {param_name})') - self.dump('{') - lines = [] - for i, value_name in enumerate(value_names): - intern_name = self.mangle_name(value_name) - if i == 0: - lines.append('if (%s == %s) {' % (value_name, param_name)) - else: - lines.append('} else if (%s == %s) {' % (value_name, param_name)) - lines.append(f'return {intern_name};') - lines.append('}') - lines.append(f'return ({intern_type}) {param_name};') - self.dump_lines(lines) - self.dump('}') - - def generate_comm_convert_fn(self): - self.generic_convert(ConvertFuncs.COMM, 'comm', 'MPI_Comm', RESERVED_COMMUNICATORS) - - def generate_comm_convert_fn_intern_to_abi(self): - self.generic_convert_reverse(ConvertOMPIToStandard.COMM, 'comm', 'MPI_Comm', RESERVED_COMMUNICATORS) - - def generate_info_convert_fn(self): - self.generic_convert(ConvertFuncs.INFO, 'info', 'MPI_Info', RESERVED_INFOS) - - def generate_file_convert_fn_intern_to_abi(self): - self.generic_convert_reverse(ConvertFuncs.FILE, 'file', 'MPI_File', RESERVED_FILES) - - def generate_datatype_convert_fn(self): - self.generic_convert(ConvertFuncs.DATATYPE, 'datatype', 'MPI_Datatype', PREDEFINED_DATATYPES) - - def generate_op_convert_fn(self): - self.generic_convert(ConvertFuncs.OP, 'op', 'MPI_Op', COLLECTIVE_OPERATIONS) - - def generate_win_convert_fn(self): - self.generic_convert(ConvertFuncs.WIN, 'win', 'MPI_Win', RESERVED_WINDOWS) - - def generate_pointer_convert_fn(self, type_, fn_name, constants): - abi_type = self.mangle_name(type_) - self.dump(f'{INLINE_ATTRS} void {fn_name}({abi_type} *ptr)') - self.dump('{') - lines = [] - for i, ompi_name in enumerate(constants): - abi_name = self.mangle_name(ompi_name) - if i == 0: - lines.append('if (%s == (%s) *ptr) {' % (ompi_name, type_)) - else: - lines.append('} else if (%s == (%s) *ptr) {' % (ompi_name, type_)) - lines.append(f'*ptr = {abi_name};') - lines.append('}') - self.dump_lines(lines) - self.dump('}') - - def generate_request_convert_fn(self): - self.generate_pointer_convert_fn('MPI_Request', ConvertFuncs.REQUEST, RESERVED_REQUESTS) - - def generate_file_convert_fn(self): - self.generate_pointer_convert_fn('MPI_File', ConvertFuncs.FILE, RESERVED_FILES) - - def generate_status_convert_fn(self): - type_ = 'MPI_Status' - abi_type = self.mangle_name(type_) - self.dump(f'{INLINE_ATTRS} void {ConvertFuncs.STATUS}({abi_type} *out, {type_} *inp)') - self.dump('{') - self.dump(' out->MPI_SOURCE = inp->MPI_SOURCE;') - self.dump(' out->MPI_TAG = inp->MPI_TAG;') - self.dump(f' out->MPI_ERROR = {ConvertFuncs.ERROR_CLASS}(inp->MPI_ERROR);') - # TODO: What to do with the private fields? - self.dump('}') - - def define(self, type_, name, value): - self.dump(f'#define {name} OMPI_CAST_CONSTANT({type_}, {value})') - - def define_all(self, type_, constants): - for i, const in enumerate(constants): - self.define(self.mangle_name(type_), self.mangle_name(const), i + 1) - self.dump() - - def dump_header(self): - header_guard = '_ABI_INTERNAL_' - self.dump(f'#ifndef {header_guard}') - self.dump(f'#define {header_guard}') - - self.dump('#include "stddef.h"') - self.dump('#include "stdint.h"') - - self.dump(""" -#if defined(c_plusplus) || defined(__cplusplus) -extern "C" { -#endif -""") - - self.dump(""" -#if defined(c_plusplus) || defined(__cplusplus) -#define OMPI_CAST_CONSTANT(type, value) (static_cast (static_cast (value))) -#else -#define OMPI_CAST_CONSTANT(type, value) ((type) ((void *) value)) -#endif -""") - - for i, err in enumerate(ERROR_CLASSES): - self.dump(f'#define {self.mangle_name(err)} {i + 1}') - self.dump() - - self.define_all('MPI_Datatype', PREDEFINED_DATATYPES) - self.define_all('MPI_Op', COLLECTIVE_OPERATIONS) - self.define_all('MPI_Comm', RESERVED_COMMUNICATORS) - self.define_all('MPI_Request', RESERVED_REQUESTS) - self.define_all('MPI_Win', RESERVED_WINDOWS) - self.define_all('MPI_Info', RESERVED_INFOS) - self.define_all('MPI_File', RESERVED_FILES) - - for name, value in VARIOUS_CONSTANTS.items(): - self.dump(f'#define {self.mangle_name(name)} {value}') - self.dump() - - status_type = self.mangle_name('MPI_Status') - for i, name in enumerate(IGNORED_STATUS_HANDLES): - self.define(f'{status_type} *', self.mangle_name(name), i + 1) - self.dump() - - for i, name in enumerate(COMMUNICATOR_SPLIT_TYPES): - self.dump(f'#define {self.mangle_name(name)} {i}') - self.dump() - - for mpi_type, c_type in C_OPAQUE_TYPES.items(): - self.dump(f'typedef {c_type} {self.mangle_name(mpi_type)};') - self.dump() - - for handle in C_HANDLES: - prefix, suffix = handle.split('_') - name = f'{prefix}_ABI_{suffix}' - self.dump(f'typedef struct {self.mangle_name(name)} *{self.mangle_name(handle)};') - self.dump() - self.dump(""" -struct MPI_Status_ABI { - int MPI_SOURCE; - int MPI_TAG; - int MPI_ERROR; - int mpi_abi_private[5]; -};""") - self.dump(f'typedef struct MPI_Status_ABI {self.mangle_name("MPI_Status")};') - self.dump() - # Function signatures - for sig in self.signatures: - self.dump(f'{sig};') - self.dump('int MPI_Abi_details(int *buflen, char *details, MPI_Info *info);') - self.dump('int MPI_Abi_supported(int *flag);') - self.dump('int MPI_Abi_version(int *abi_major, int *abi_minor);') - if not self.external: - # Now generate the conversion code - self.generate_error_convert_fn() - self.generate_comm_convert_fn() - self.generate_comm_convert_fn_intern_to_abi() - self.generate_info_convert_fn() - self.generate_file_convert_fn() - self.generate_datatype_convert_fn() - self.generate_op_convert_fn() - self.generate_win_convert_fn() - self.generate_request_convert_fn() - self.generate_status_convert_fn() - - self.dump(""" -#if defined(c_plusplus) || defined(__cplusplus) -} -#endif -""") - self.dump(f'#endif /* {header_guard} */') - - -class Parameter: - - def __init__(self, text): - """Parse a parameter.""" - # parameter in the form "TYPE NAME" or "TYPE NAME:COUNT_VAR" - type_, namecount = text.split() - if ':' in namecount: - name, count_param = namecount.split(':') - else: - name, count_param = namecount, None - self.type_ = type_ - self.name = name - self.count_param = count_param - - def construct(self, abi_type, **kwargs): - """Construct the type parameter for the given ABI.""" - return Type.construct(abi_type, type_=self.type_, name=self.name, - count_param=self.count_param, **kwargs) - - -class ReturnType: - """Return type wrapper.""" - - def __init__(self, type_): - self.type_ = type_ - - def construct(self, abi_type, **kwargs): - """Construct the return type for the given ABI.""" - return Type.construct(abi_type, type_=self.type_, **kwargs) - - -class Type(ABC): - """Type representation.""" - - PARAMS_OMPI_ABI = {} - - PARAMS_STANDARD_ABI = {} - - def __init__(self, type_, name=None, - mangle_name=lambda name: abi_internal_name(name), - count_param=None, **kwargs): - self.type = type_ - self.name = name - self.count_param = count_param - self.mangle_name = mangle_name - - @staticmethod - def construct(abi_type, type_, **kwargs): - """Construct the parameter for the given ABI and type.""" - if abi_type == 'ompi': - return Type.PARAMS_OMPI_ABI[type_](type_, **kwargs) - elif abi_type == 'standard': - return Type.PARAMS_STANDARD_ABI[type_](type_, **kwargs) - else: - raise RuntimeError(f'invalid ABI type {abi_type}') - - @staticmethod - def add_type(type_name, abi_type=('ompi', 'standard')): - """Add a new class corresponding to a type.""" - def wrapper(class_): - if 'ompi' in abi_type: - Type.PARAMS_OMPI_ABI[type_name] = class_ - if 'standard' in abi_type: - Type.PARAMS_STANDARD_ABI[type_name] = class_ - # Parameter.TYPES[type_] = class_ - return class_ - return wrapper - - @property - def is_count(self): - """Return True if this parameter is a count (requiring bigcount API).""" - return False - - @property - def init_code(self): - """Return the initialization code needed for an ABI wrapper.""" - return [] - - @property - def final_code(self): - """Return the finalization code needed for an ABI wrapper.""" - return [] - - def return_code(self, name): - """Process a value and then build up a return statement.""" - return [f'return {name};'] - - @property - def argument(self): - """Return the argument text required for passing an argument to a function.""" - return self.name - - @abstractmethod - def type_text(self, count_type=None): - """Return the source text corresponding to a type definition.""" - - def tmp_type_text(self, count_type=None): - """Return source text corresponding to a temporary type definition before conversion.""" - return self.type_text(count_type=count_type) - - def parameter(self, count_type=None, **kwargs): - return f'{self.type_text(count_type)} {self.name}' - - -@Type.add_type('ERROR_CLASS') -class TypeErrorClass(Type): - - def type_text(self, count_type=None): - return 'int' - - def return_code(self, name): - return [f'return {ConvertFuncs.ERROR_CLASS}({name});'] - - -@Type.add_type('BUFFER') -class TypeBuffer(Type): - - def type_text(self, count_type=None): - return 'const void *' - - -@Type.add_type('BUFFER_OUT') -class TypeBufferOut(Type): - - def type_text(self, count_type=None): - return f'void *' - - -@Type.add_type('COUNT') -class TypeCount(Type): - - @property - def is_count(self): - return True - - def type_text(self, count_type=None): - return 'int' if count_type is None else count_type - - -@Type.add_type('INT') -class TypeBufferOut(Type): - - def type_text(self, count_type=None): - return 'int' - - -@Type.add_type('AINT') -class TypeBufferOut(Type): - - def type_text(self, count_type=None): - return 'MPI_Aint' - - -@Type.add_type('INT_OUT') -class TypeBufferOut(Type): - - def type_text(self, count_type=None): - return 'int *' - - def parameter(self, count_type=None, **kwargs): - if self.count_param is None: - return f'int *{self.name}' - else: - return f'int {self.name}[]' - - -@Type.add_type('DOUBLE') -class TypeDouble(Type): - - def type_text(self, count_type=None): - return 'double' - - -@Type.add_type('ARGV') -class TypeArgv(Type): - - def type_text(self, count_type=None): - return 'char ***' - - -@Type.add_type('DATATYPE', abi_type=['ompi']) -class TypeDatatype(Type): - - def type_text(self, count_type=None): - return 'MPI_Datatype' - - -class StandardABIType(Type): - - @property - def tmpname(self): - return f'{self.name}_tmp' - - @property - def argument(self): - return self.tmpname - - -@Type.add_type('DATATYPE', abi_type=['standard']) -class TypeDatatype(StandardABIType): - - @property - def init_code(self): - return [f'MPI_Datatype {self.tmpname} = {ConvertFuncs.DATATYPE}({self.name});'] - - def type_text(self, count_type=None): - return self.mangle_name('MPI_Datatype') - - -@Type.add_type('OP', abi_type=['ompi']) -class TypeDatatype(Type): - - def type_text(self, count_type=None): - return 'MPI_Op' - - -@Type.add_type('OP', abi_type=['standard']) -class TypeDatatype(StandardABIType): - - @property - def init_code(self): - return [f'MPI_Op {self.tmpname} = {ConvertFuncs.OP}({self.name});'] - - def type_text(self, count_type=None): - return self.mangle_name('MPI_Op') - - -@Type.add_type('RANK') -class TypeRank(Type): - - def type_text(self, count_type=None): - return 'int' - - -@Type.add_type('TAG') -class TypeRank(Type): - - def type_text(self, count_type=None): - return 'int' - - -@Type.add_type('COMM', abi_type=['ompi']) -class TypeCommunicator(Type): - - def type_text(self, count_type=None): - return 'MPI_Comm' - - -@Type.add_type('COMM', abi_type=['standard']) -class TypeCommunicatorStandard(StandardABIType): - - @property - def init_code(self): - return [f'MPI_Comm {self.tmpname} = {ConvertFuncs.COMM}({self.name});'] - - def tmp_type_text(self, count_type=None): - return 'MPI_Comm' - - def return_code(self, name): - return [f'return {ConvertOMPIToStandard.COMM}({name});'] - - def type_text(self, count_type=None): - return self.mangle_name('MPI_Comm') - - -@Type.add_type('COMM_OUT', abi_type=['ompi']) -class TypeCommunicator(Type): - - def type_text(self, count_type=None): - return 'MPI_Comm *' - - -@Type.add_type('COMM_OUT', abi_type=['standard']) -class TypeCommunicator(Type): - - @property - def final_code(self): - return [f'*{self.name} = {ConvertOMPIToStandard.COMM}((MPI_Comm) *{self.name});'] - - def type_text(self, count_type=None): - type_name = self.mangle_name('MPI_Comm') - return f'{type_name} *' - - @property - def argument(self): - return f'(MPI_Comm *) {self.name}' - - -@Type.add_type('WIN', abi_type=['ompi']) -class TypeWindow(Type): - - def type_text(self, count_type=None): - return 'MPI_Win' - - -@Type.add_type('WIN', abi_type=['standard']) -class TypeWindowStandard(StandardABIType): - - @property - def init_code(self): - return [f'MPI_Win {self.tmpname} = {ConvertFuncs.WIN}({self.name});'] - - def type_text(self, count_type=None): - return self.mangle_name('MPI_Win') - - -@Type.add_type('REQUEST', abi_type=['ompi']) -class TypeRequest(Type): - - def type_text(self, count_type=None): - return 'MPI_Request' - - -@Type.add_type('REQUEST', abi_type=['standard']) -class TypeRequestStandard(Type): - - def type_text(self, count_type=None): - return self.mangle_name('MPI_Request') - - @property - def argument(self): - return f'(MPI_Request) {self.name}' - - -@Type.add_type('REQUEST_INOUT', abi_type=['ompi']) -class TypeRequestInOut(Type): - - def type_text(self, count_type=None): - return 'MPI_Request *' - - -@Type.add_type('REQUEST_INOUT', abi_type=['standard']) -class TypeRequestInOutStandard(Type): - - @property - def final_code(self): - if self.count_param is None: - return [f'{ConvertFuncs.REQUEST}({self.name});'] - else: - return [ - 'for (int i = 0; i < %s; ++i) {' % (self.count_param,), - f'{ConvertFuncs.REQUEST}(&{self.name}[i]);', - '}', - ] - - @property - def argument(self): - return f'(MPI_Request *) {self.name}' - - def type_text(self, count_type=None): - type_name = self.mangle_name('MPI_Request') - return f'{type_name} *' - - def parameter(self, count_type=None, **kwargs): - type_name = self.mangle_name('MPI_Request') - if self.count_param is None: - return f'{type_name} *{self.name}' - else: - return f'{type_name} {self.name}[]' - - -@Type.add_type('STATUS_OUT', abi_type=['ompi']) -class TypeStatusOut(Type): - - def type_text(self, count_type=None): - return 'MPI_Status *' - - def parameter(self, count_type=None, **kwargs): - if self.count_param is None: - return f'MPI_Status *{self.name}' - else: - return f'MPI_Status {self.name}[]' - - -@Type.add_type('STATUS_OUT', abi_type=['standard']) -class TypeStausOutStandard(StandardABIType): - - def if_should_set_status(self): - """Generate the condition to check if the status(es) should be set.""" - condition = ' && '.join(f'{self.mangle_name(const)} != {self.name}' - for const in IGNORED_STATUS_HANDLES) - return 'if (%s) {' % (condition,) - - @property - def status_argument(self): - return f'{self.name}_arg' - - @property - def init_code(self): - code = [f'MPI_Status *{self.status_argument} = NULL;'] - if self.count_param is None: - code.append(f'MPI_Status {self.tmpname};') - else: - code.append(f'MPI_Status *{self.tmpname} = NULL;') - code.append(self.if_should_set_status()) - if self.count_param is not None: - code.append(f'{self.tmpname} = malloc({self.count_param} * sizeof(MPI_Status));') - code.append(f'{self.status_argument} = {self.tmpname};') - else: - code.append(f'{self.status_argument} = &{self.tmpname};') - code.append('} else {') - if self.count_param is not None: - code.append(f'{self.status_argument} = MPI_STATUSES_IGNORE;') - else: - code.append(f'{self.status_argument} = MPI_STATUS_IGNORE;') - code.append('}') - return code - - @property - def final_code(self): - code = [self.if_should_set_status()] - if self.count_param is None: - code.append(f'{ConvertFuncs.STATUS}({self.name}, &{self.tmpname});') - else: - code.extend([ - 'for (int i = 0; i < %s; ++i) {' % (self.count_param,), - f'{ConvertFuncs.STATUS}(&{self.name}[i], &{self.tmpname}[i]);', - '}', - f'free({self.tmpname});', - ]) - code.append('}') - return code - - @property - def argument(self): - return self.status_argument - - def type_text(self, count_type=None): - type_name = self.mangle_name('MPI_Status') - return f'{type_name} *' - - def parameter(self, count_type=None, **kwargs): - type_name = self.mangle_name('MPI_Status') - if self.count_param is None: - return f'{type_name} *{self.name}' - else: - return f'{type_name} {self.name}[]' - - -# For now this just assumes that MPI_Fint doesn't need any conversions -@Type.add_type('FINT') -class TypeFint(Type): - - def type_text(self, count_type=None): - return 'MPI_Fint' - - -@Type.add_type('STRING') -class TypeString(Type): - - def type_text(self, count_type=None): - return 'const char *' - - -@Type.add_type('STRING_OUT') -class TypeStringOut(Type): - - def type_text(self, count_type=None): - return 'char *' - - -@Type.add_type('INFO', abi_type=['ompi']) -class TypeInfo(Type): - - def type_text(self, count_type=None): - return 'MPI_Info' - - -@Type.add_type('INFO', abi_type=['standard']) -class TypeInfoStandard(StandardABIType): - - @property - def init_code(self): - return [f'MPI_Info {self.tmpname} = {ConvertFuncs.INFO}({self.name});'] - - def type_text(self, count_type=None): - return self.mangle_name('MPI_Info') - - -@Type.add_type('FILE_OUT', abi_type=['ompi']) -class TypeFileOut(Type): - - def type_text(self, count_type=None): - return 'MPI_File *' - - -@Type.add_type('FILE_OUT', abi_type=['standard']) -class TypeFileOutStandard(Type): - - @property - def argument(self): - return f'(MPI_File *) {self.name}' - - @property - def final_code(self): - return [f'{ConvertFuncs.FILE}({self.name});'] - - def type_text(self, count_type=None): - type_name = self.mangle_name('MPI_File') - return f'{type_name} *' - - -class Prototype: - """MPI function prototype.""" - - def __init__(self, name, return_type, params): - self.name = name - self.return_type = return_type - self.params = params - - def signature(self, abi_type, fn_name, count_type=None, **kwargs): - """Build a signature with the given name and count_type.""" - params = ', '.join(param.construct(abi_type, **kwargs).parameter(count_type=count_type, **kwargs) - for param in self.params) - if not params: - params = 'void' - return_type_text = self.return_type.construct(abi_type, **kwargs).type_text(count_type=count_type) - return f'{return_type_text} {fn_name}({params})' - - @property - def need_bigcount(self): - """Check if a bigcount interface is required for a prototype.""" - return any('COUNT' in param.type_ for param in self.params) - - -class TemplateParseError(Exception): - """Error raised during parsing.""" - pass - - -def validate_body(body): - """Validate the body of a template.""" - # Just do a simple bracket balance test to determine the bounds of the - # function body. All lines after the function body should be blank. There - # are cases where this will break, such as if someone puts code all on one - # line. - bracket_balance = 0 - line_count = 0 - for line in body: - line = line.strip() - if bracket_balance == 0 and line_count > 0 and line: - raise TemplateParseError('Extra code found in template; only one function body is allowed') - - update = line.count('{') - line.count('}') - bracket_balance += update - if bracket_balance != 0: - line_count += 1 - - if bracket_balance != 0: - raise TemplateParseError('Mismatched brackets found in template') - - -class SourceTemplate: - """Source template for a single API function.""" - - def __init__(self, prototype, header, body): - self.prototype = prototype - self.header = header - self.body = body - - @staticmethod - def load(fname, prefix=None): - """Load a template file and return the SourceTemplate.""" - if prefix is not None: - fname = os.path.join(prefix, fname) - with open(fname) as fp: - header = [] - prototype = [] - body = [] - - for line in fp: - line = line.rstrip() - if prototype and line.startswith('PROTOTYPE'): - raise TemplateParseError('more than one prototype found in template file') - elif ((prototype and not any(')' in s for s in prototype)) - or line.startswith('PROTOTYPE')): - prototype.append(line) - elif prototype: - # Validate bracket balance - body.append(line) - else: - header.append(line) - - if not prototype: - raise RuntimeError('missing prototype') - # Parse the prototype - prototype = ''.join(prototype) - prototype = prototype[len('PROTOTYPE'):] - i = prototype.index('(') - j = prototype.index(')') - return_type, name = prototype[:i].split() - return_type = ReturnType(return_type) - params = [param.strip() for param in prototype[i + 1:j].split(',') if param.strip()] - params = [Parameter(param) for param in params] - prototype = Prototype(name, return_type, params) - # Ensure the body contains only one function - validate_body(body) - return SourceTemplate(prototype, header, body) - - def print_header(self, file=sys.stdout): - """Print the source header.""" - for line in self.header: - print(line, file=file) - - def print_body(self, func_name, file=sys.stdout): - """Print the body.""" - for line in self.body: - # FUNC_NAME is used for error messages - line = line.replace('FUNC_NAME', f'"{func_name}"') - print(line, file=file) - - -def print_profiling_header(fn_name, file=sys.stdout): - """Print the profiling header code.""" - print('#if OMPI_BUILD_MPI_PROFILING') - print('#if OPAL_HAVE_WEAK_SYMBOLS', file=file) - print(f'#pragma weak {fn_name} = P{fn_name}', file=file) - print('#endif', file=file) - print(f'#define {fn_name} P{fn_name}', file=file) - print('#endif') - - -def ompi_abi(base_name, template): - """Generate the OMPI ABI functions.""" - template.print_header() - print_profiling_header(base_name) - print(template.prototype.signature('ompi', base_name)) - template.print_body(func_name=base_name) - # Check if we need to generate the bigcount interface - if template.prototype.need_bigcount: - base_name_c = f'{base_name}_c' - print_profiling_header(base_name_c) - print(template.prototype.signature('ompi', base_name_c, count_type='MPI_Count')) - template.print_body(func_name=base_name_c) - - -ABI_INTERNAL_HEADER = 'ompi/mpi/c/abi.h' - - -def indent_lines(lines, tab, start=0): - """Crude pretty-printing function.""" - new_lines = [] - indent_count = start - for line in lines: - # Closing bracket - if '}' in line: - indent_count -= 1 - - prefix = indent_count * tab - new_lines.append(f'{prefix}{line}') - - # Opening bracket - if '{' in line: - indent_count += 1 - return new_lines - - -def standard_abi(base_name, template): - """Generate the standard ABI functions.""" - template.print_header() - print(f'#include "{ABI_INTERNAL_HEADER}"') - - # Static internal function (add a random component to avoid conflicts) - internal_name = f'ompi_abi_{template.prototype.name}' - internal_sig = template.prototype.signature('ompi', internal_name, - count_type='MPI_Count') - print(INLINE_ATTRS, internal_sig) - template.print_body(func_name=base_name) - - def generate_function(prototype, fn_name, internal_fn, count_type='int'): - """Generate a function for the standard ABI.""" - print_profiling_header(fn_name) - - # Handle type conversions and arguments - params = [param.construct('standard') for param in prototype.params] - print(prototype.signature('standard', fn_name, count_type=count_type)) - print('{') - lines = [] - return_type = prototype.return_type.construct('standard') - lines.append(f'{return_type.tmp_type_text()} ret_value;') - for param in params: - if param.init_code: - lines.extend(param.init_code) - pass_args = ', '.join(param.argument for param in params) - lines.append(f'ret_value = {internal_fn}({pass_args});') - for param in params: - if param.final_code: - lines.extend(param.final_code) - lines.extend(return_type.return_code('ret_value')) - - # Indent the lines - lines = indent_lines(lines, 4 * ' ', start=1) - for line in lines: - print(line) - print('}') - - generate_function(template.prototype, base_name, internal_name) - if template.prototype.need_bigcount: - base_name_c = f'{base_name}_c' - generate_function(template.prototype, base_name_c, internal_name, - count_type='MPI_Count') - - -def gen_header(args): - """Generate an ABI header and conversion code.""" - prototypes = [SourceTemplate.load(file_, args.srcdir).prototype for file_ in args.file] - - builder = ABIHeaderBuilder(prototypes, external=args.external) - builder.dump_header() - - -def gen_source(args): - """Generate source file.""" - template = SourceTemplate.load(args.source_file) - - base_name = mpi_fn_name_from_base_fn_name(template.prototype.name) - if args.type == 'ompi': - ompi_abi(base_name, template) - else: - standard_abi(base_name, template) - - -def main(): - if len(sys.argv) < 2: - # Fix required for Python 3.6 - print('ERROR: missing subparser argument (see --help)') - sys.exit(1) - - parser = argparse.ArgumentParser(description='generate ABI header file and conversion code') - subparsers = parser.add_subparsers() - - parser_header = subparsers.add_parser('header') - parser_header.add_argument('file', nargs='+', help='list of template source files') - parser_header.add_argument('--external', action='store_true', help='generate external mpi.h header file') - parser_header.add_argument('--srcdir', help='source directory') - parser_header.set_defaults(func=gen_header) - - parser_gen = subparsers.add_parser('source') - # parser = argparse.ArgumentParser(description='C ABI binding generation code') - parser_gen.add_argument('type', choices=('ompi', 'standard'), - help='generate the OMPI ABI functions or the standard ABI functions') - parser_gen.add_argument('source_file', help='source template file') - parser_gen.set_defaults(func=gen_source) - - args = parser.parse_args() - - # Always add the header - print('/* THIS FILE WAS AUTOGENERATED BY ompi/mpi/c/abi.py. DO NOT EDIT BY HAND. */') - args.func(args) - - -if __name__ == '__main__': - main() diff --git a/ompi/mpi/fortran/use-mpi-f08/Makefile.am b/ompi/mpi/fortran/use-mpi-f08/Makefile.am index ac8b7898356..18c59ffb7ee 100644 --- a/ompi/mpi/fortran/use-mpi-f08/Makefile.am +++ b/ompi/mpi/fortran/use-mpi-f08/Makefile.am @@ -126,9 +126,7 @@ mpi_api_files = \ allreduce_f08.F90 \ allreduce_init_f08.F90 \ alltoall_init_f08.F90 \ - alltoallv_f08.F90 \ alltoallv_init_f08.F90 \ - alltoallw_f08.F90 \ alltoallw_init_f08.F90 \ barrier_f08.F90 \ barrier_init_f08.F90 \ @@ -588,8 +586,15 @@ EXTRA_DIST = \ DISTCLEANFILES = api_f08_generated.F90 if OMPI_GENERATE_BINDINGS -api_f08_generated.F90: interface.in generate_bindings.py - $(PYTHON) $(srcdir)/generate_bindings.py fortran $< > $@ +api_f08_generated.F90: interface.in + $(PYTHON) $(top_srcdir)/ompi/mpi/bindings/bindings.py \ + --builddir $(abs_top_builddir) \ + --srcdir $(abs_top_srcdir) \ + --output $(abs_builddir)/$@ \ + fortran \ + --template $(abs_srcdir)/$< \ + code \ + fortran endif ########################################################################### diff --git a/ompi/mpi/fortran/use-mpi-f08/alltoallv_f08.F90 b/ompi/mpi/fortran/use-mpi-f08/alltoallv_f08.F90 deleted file mode 100644 index 0acf0fd03a5..00000000000 --- a/ompi/mpi/fortran/use-mpi-f08/alltoallv_f08.F90 +++ /dev/null @@ -1,32 +0,0 @@ -! -*- f90 -*- -! -! Copyright (c) 2009-2012 Cisco Systems, Inc. All rights reserved. -! Copyright (c) 2009-2012 Los Alamos National Security, LLC. -! All rights reserved. -! Copyright (c) 2018-2020 Research Organization for Information Science -! and Technology (RIST). All rights reserved. -! $COPYRIGHT$ - -#include "ompi/mpi/fortran/configure-fortran-output.h" - -#include "mpi-f08-rename.h" - -subroutine MPI_Alltoallv_f08(sendbuf,sendcounts,sdispls,sendtype,recvbuf,& - recvcounts,rdispls,recvtype,comm,ierror) - use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm - use :: ompi_mpifh_bindings, only : ompi_alltoallv_f - implicit none - OMPI_FORTRAN_IGNORE_TKR_TYPE, INTENT(IN) :: sendbuf - OMPI_FORTRAN_IGNORE_TKR_TYPE :: recvbuf - INTEGER, INTENT(IN) :: sendcounts(*), sdispls(*), recvcounts(*), rdispls(*) - TYPE(MPI_Datatype), INTENT(IN) :: sendtype - TYPE(MPI_Datatype), INTENT(IN) :: recvtype - TYPE(MPI_Comm), INTENT(IN) :: comm - INTEGER, OPTIONAL, INTENT(OUT) :: ierror - integer :: c_ierror - - call ompi_alltoallv_f(sendbuf,sendcounts,sdispls,sendtype%MPI_VAL,& - recvbuf,recvcounts,rdispls,recvtype%MPI_VAL,comm%MPI_VAL,c_ierror) - if (present(ierror)) ierror = c_ierror - -end subroutine MPI_Alltoallv_f08 diff --git a/ompi/mpi/fortran/use-mpi-f08/alltoallw_f08.F90 b/ompi/mpi/fortran/use-mpi-f08/alltoallw_f08.F90 deleted file mode 100644 index f63ac4842f7..00000000000 --- a/ompi/mpi/fortran/use-mpi-f08/alltoallw_f08.F90 +++ /dev/null @@ -1,42 +0,0 @@ -! -*- f90 -*- -! -! Copyright (c) 2009-2013 Cisco Systems, Inc. All rights reserved. -! Copyright (c) 2009-2012 Los Alamos National Security, LLC. -! All rights reserved. -! Copyright (c) 2018-2020 Research Organization for Information Science -! and Technology (RIST). All rights reserved. -! $COPYRIGHT$ - -#include "ompi/mpi/fortran/configure-fortran-output.h" - -#include "mpi-f08-rename.h" - -subroutine MPI_Alltoallw_f08(sendbuf,sendcounts,sdispls,sendtypes,& - recvbuf,recvcounts,rdispls,recvtypes,comm,ierror) - use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm - use :: ompi_mpifh_bindings, only : ompi_alltoallw_f - implicit none - OMPI_FORTRAN_IGNORE_TKR_TYPE, INTENT(IN) :: sendbuf - OMPI_FORTRAN_IGNORE_TKR_TYPE :: recvbuf - INTEGER, INTENT(IN) :: sendcounts(*), sdispls(*), recvcounts(*), rdispls(*) - TYPE(MPI_Datatype), INTENT(IN) :: sendtypes(*) - TYPE(MPI_Datatype), INTENT(IN) :: recvtypes(*) - TYPE(MPI_Comm), INTENT(IN) :: comm - INTEGER, OPTIONAL, INTENT(OUT) :: ierror - integer :: c_ierror - - ! Note that we pass a scalar here for both the sendtypes and - ! recvtypes arguments, even though the real Alltoallw function - ! expects an array of integers. This is a hack: we know that - ! [send|recv]types(1)%MPI_VAL will pass the address of the first - ! integer in the array of Type(MPI_Datatype) derived types. And - ! since Type(MPI_Datatype) are exactly memory-equivalent to a - ! single INTEGER, passing the address of the first one is the same - ! as passing the address to an array of integers. To be clear: the - ! back-end ompi_alltoallw_f is expecting a pointer to an array of - ! integers. So it all works out (but is a hack :-\ ). - call ompi_alltoallw_f(sendbuf,sendcounts,sdispls,sendtypes(1)%MPI_VAL,& - recvbuf,recvcounts,rdispls,recvtypes(1)%MPI_VAL,comm%MPI_VAL,c_ierror) - if (present(ierror)) ierror = c_ierror - -end subroutine MPI_Alltoallw_f08 diff --git a/ompi/mpi/fortran/use-mpi-f08/base/Makefile.am b/ompi/mpi/fortran/use-mpi-f08/base/Makefile.am index 76ebb0aed41..a7cd4fd79ad 100644 --- a/ompi/mpi/fortran/use-mpi-f08/base/Makefile.am +++ b/ompi/mpi/fortran/use-mpi-f08/base/Makefile.am @@ -34,11 +34,24 @@ libusempif08_ccode_la_SOURCES = \ buffer_detach.c \ api_f08_generated.c -if OMPI_GENERATE_BINDINGS -api_f08_generated.c: ../interface.in $(srcdir)/../generate_bindings.py - $(PYTHON) $(srcdir)/../generate_bindings.py c $< > $@ +if OMPI_FORTRAN_HAVE_TS +libusempif08_ccode_la_SOURCES += \ + ts.h \ + ts.c +endif -MAINTAINERCLEANFILES = api_f08_generated.c +DISTCLEANFILES = api_f08_generated.c + +if OMPI_GENERATE_BINDINGS +api_f08_generated.c: ../interface.in + $(PYTHON) $(top_srcdir)/ompi/mpi/bindings/bindings.py \ + --builddir $(abs_top_builddir) \ + --srcdir $(abs_top_srcdir) \ + --output $(abs_builddir)/$@ \ + fortran \ + --template $(abs_srcdir)/$< \ + code \ + c endif endif diff --git a/ompi/mpi/fortran/use-mpi-f08/base/ts.c b/ompi/mpi/fortran/use-mpi-f08/base/ts.c new file mode 100644 index 00000000000..290f24feea7 --- /dev/null +++ b/ompi/mpi/fortran/use-mpi-f08/base/ts.c @@ -0,0 +1,136 @@ +/* -*- Mode: C; c-basic-offset:4 ; -*- */ +/* + * Copyright (c) 2014 Argonne National Laboratory. + * Copyright (c) 2019 Research Organization for Information Science + * and Technology (RIST). All rights reserved. + * $COPYRIGHT$ + * + * Additional copyrights may follow + * + * $HEADER$ + */ + +#include "ts.h" + +#include + +int ompi_ts_create_datatype(CFI_cdesc_t *cdesc, int oldcount, MPI_Datatype oldtype, MPI_Datatype *newtype) +{ + const int MAX_RANK = 15; /* Fortran 2008 specifies a maximum rank of 15 */ + MPI_Datatype types[MAX_RANK + 1]; /* Use a fixed size array to avoid malloc. + 1 for oldtype */ + int mpi_errno = MPI_SUCCESS; + int accum_elems = 1; + int accum_sm = cdesc->elem_len; + int done = 0; /* Have we created a datatype for oldcount of oldtype? */ + int last; /* Index of the last successfully created datatype in types[] */ + int extent; + int i, j; + +#ifdef OPAL_ENABLE_DEBUG + { + size_t size; + assert(cdesc->rank <= MAX_RANK); + ompi_datatype_type_size(oldtype, &size); + /* When cdesc->elem_len != size, things suddenly become complicated. Generally, it is hard to create + * a composite datatype based on two datatypes. Currently we don't support it and doubt it is usefull. + */ + assert(cdesc->elem_len == size); + } +#endif + + types[0] = oldtype; + i = 0; + done = 0; + while (i < cdesc->rank && !done) { + if (oldcount % accum_elems) { + /* oldcount should be a multiple of accum_elems, otherwise we might need an + * MPI indexed datatype to describle the irregular region, which is not supported yet. + */ + mpi_errno = MPI_ERR_INTERN; + last = i; + goto fn_exit; + } + + extent = oldcount / accum_elems; + if (extent > cdesc->dim[i].extent) { + extent = cdesc->dim[i].extent; + } else { + /* Up to now, we have accumlated enough elements */ + done = 1; + } + + if (cdesc->dim[i].sm == accum_sm) { + mpi_errno = PMPI_Type_contiguous(extent, types[i], &types[i+1]); + } else { + mpi_errno = PMPI_Type_create_hvector(extent, 1, cdesc->dim[i].sm, types[i], &types[i+1]); + } + if (mpi_errno != MPI_SUCCESS) { + last = i; + goto fn_exit; + } + + accum_sm = cdesc->dim[i].sm * cdesc->dim[i].extent; + accum_elems *= cdesc->dim[i].extent; + i++; + } + + if (done) { + *newtype = types[i]; + MPI_Type_commit(newtype); + last = i - 1; /* To avoid freeing newtype */ + } else { + /* If # of elements given by "oldcount oldtype" is bigger than + * what cdesc describles, then we will reach here. + */ + last = i; + mpi_errno = MPI_ERR_ARG; + goto fn_exit; + } + +fn_exit: + for (j = 1; j <= last; j++) + PMPI_Type_free(&types[j]); + return mpi_errno; +} + +static void copy(CFI_dim_t *dim, int rank, char * base, char **dest, size_t len) { + for (CFI_index_t i=0; iextent; i++) { + if (rank > 1) { + copy(dim-1, rank-1, base, dest, len); + } else { + memcpy(*dest, base, len); + *dest += len; + } + base += dim->sm; + } +} + +int ompi_ts_copy(CFI_cdesc_t *cdesc, char *buffer) { + copy(&cdesc->dim[cdesc->rank - 1], cdesc->rank, cdesc->base_addr, &buffer, cdesc->elem_len); + return OMPI_SUCCESS; +} + +static void copy_back(CFI_dim_t *dim, int rank, char * base, char **source, size_t len) { + for (CFI_index_t i=0; iextent; i++) { + if (rank > 1) { + copy_back(dim-1, rank-1, base, source, len); + } else { + memcpy(base, *source, len); + *source += len; + } + base += dim->sm; + } +} + +int ompi_ts_copy_back(char *buffer, CFI_cdesc_t *cdesc) { + copy_back(&cdesc->dim[cdesc->rank - 1], cdesc->rank, cdesc->base_addr, &buffer, cdesc->elem_len); + return OMPI_SUCCESS; +} + +size_t ompi_ts_size(CFI_cdesc_t *cdesc) { + size_t res = cdesc->elem_len; + for (int i=0; irank; i++) { + res *= cdesc->dim[i].extent; + } + return res; +} diff --git a/ompi/mpi/fortran/use-mpi-f08/base/ts.h b/ompi/mpi/fortran/use-mpi-f08/base/ts.h new file mode 100644 index 00000000000..1f134eaae65 --- /dev/null +++ b/ompi/mpi/fortran/use-mpi-f08/base/ts.h @@ -0,0 +1,107 @@ +/* -*- Mode: C; c-basic-offset:4 ; -*- */ +/* + * Copyright (c) 2014 Argonne National Laboratory. + * Copyright (c) 2019 Research Organization for Information Science + * and Technology (RIST). All rights reserved. + * $COPYRIGHT$ + * + * Additional copyrights may follow + * + * $HEADER$ + */ + +#include "ompi_config.h" + +#include + +#include "ompi/datatype/ompi_datatype.h" +#include "ompi/mpi/fortran/base/fint_2_int.h" + +extern int ompi_ts_create_datatype(CFI_cdesc_t *cdesc, int oldcount, MPI_Datatype oldtype, MPI_Datatype *newtype); + +extern size_t ompi_ts_size(CFI_cdesc_t *cdesc); + +extern int ompi_ts_copy_back(char *buffer, CFI_cdesc_t *cdesc); + +extern int ompi_ts_copy(CFI_cdesc_t *cdesc, char *buffer); + +#define OMPI_CFI_2_C(x, count, type, datatype, rc) \ + do { \ + datatype = type; \ + if (x->rank != 0 && !CFI_is_contiguous(x)) { \ + rc = ompi_ts_create_datatype(x, count, type, &datatype); \ + if (MPI_SUCCESS == rc) { \ + count = 1; \ + } \ + } else { \ + rc = MPI_SUCCESS; \ + } \ + } while (0) + +#define OMPI_CFI_2_C_ALLOC(x, buffer, count, type, datatype, rc) \ + do { \ + datatype = type; \ + if (x->rank != 0 && !CFI_is_contiguous(x)) { \ + size_t size = ompi_ts_size(x); \ + buffer = malloc(size); \ + if (NULL == buffer) { \ + rc = MPI_ERR_NO_MEM; \ + } else { \ + rc = MPI_SUCCESS; \ + } \ + } else { \ + buffer = x->base_addr; \ + rc = MPI_SUCCESS; \ + } \ + } while (0) + +#define OMPI_CFI_2_C_COPY(x, buffer, count, type, datatype, rc) \ + do { \ + datatype = type; \ + if (x->rank != 0 && !CFI_is_contiguous(x)) { \ + size_t size = ompi_ts_size(x); \ + buffer = malloc(size); \ + if (NULL == buffer) { \ + rc = MPI_ERR_NO_MEM; \ + } else { \ + rc = ompi_ts_copy(x, buffer); \ + } \ + } else { \ + buffer = x->base_addr; \ + rc = MPI_SUCCESS; \ + } \ + } while (0) + +#define OMPI_C_2_CFI_FREE(x, buffer, count, type, datatype, rc) \ + do { \ + if (buffer != x->base_addr) { \ + free(buffer); \ + } \ + if (type != datatype) { \ + rc = PMPI_Type_free(&datatype); \ + } \ + } while (0) + +#define OMPI_C_2_CFI_COPY(x, buffer, count, type, datatype, rc) \ + do { \ + if (buffer != x->base_addr) { \ + rc = ompi_ts_copy_back(buffer, x); \ + free(buffer); \ + } \ + if (type != datatype) { \ + rc = PMPI_Type_free(&datatype); \ + } \ + } while (0) + +#define OMPI_CFI_IS_CONTIGUOUS(x) \ + (0 == x->rank || CFI_is_contiguous(x)) + +#define OMPI_CFI_CHECK_CONTIGUOUS(x, rc) \ + do { \ + if (OMPI_CFI_IS_CONTIGUOUS(x)) { \ + rc = MPI_SUCCESS; \ + } else { \ + rc = MPI_ERR_INTERN; \ + } \ + } while (0) + diff --git a/ompi/mpi/fortran/use-mpi-f08/generate_bindings.py.in b/ompi/mpi/fortran/use-mpi-f08/generate_bindings.py.in deleted file mode 100644 index 37d0fdad8f7..00000000000 --- a/ompi/mpi/fortran/use-mpi-f08/generate_bindings.py.in +++ /dev/null @@ -1,781 +0,0 @@ -# Copyright (c) 2023 Triad National Security, LLC. All rights -# reserved. -# -# $COPYRIGHT$ -# -# Additional copyrights may follow -# -# $HEADER$ -# -"""Fortran binding generation code. - -This takes as input a *.in file containing a list of prototypes for Fortran -subroutines with generic types. Using this file, it can generate the Fortran -subroutines in one file and the C wraping code in another for all prototypes -listed. -""" -from abc import ABC, abstractmethod -import argparse -from collections import namedtuple -import re - -FORTRAN_ERROR_NAME = 'ierror' -C_ERROR_NAME = 'ierr' -C_ERROR_TMP_NAME = 'c_ierr' -GENERATED_MESSAGE = 'THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT BY HAND.' -PROTOTYPE_RE = re.compile(r'^\w+\((\s*\w+\s+\w+(:\w+)?\s*,?)+\)$') - - -class FortranType(ABC): - - def __init__(self, name, fn_name, bigcount=False, **kwargs): - self.name = name - self.fn_name = fn_name - self.bigcount = bigcount - # A dependent type/parameter, such as a count - self.dep_param = None - self.used_counters = 0 - - TYPES = {} - - @classmethod - def add(cls, type_name): - """Decorator for adding types.""" - def wrapper(class_): - cls.TYPES[type_name] = class_ - return class_ - return wrapper - - @classmethod - def get(cls, type_name): - return cls.TYPES[type_name] - - @property - def fn_api_name(self): - """Return the MPI API name to be used in error messages, etc..""" - return c_api_func_name(self.fn_name, bigcount=self.bigcount).upper() - - @property - def tmp_name(self): - """Return a temporary name for use in C.""" - return f'c_{self.name}' - - @property - def tmp_name2(self): - """Return a secondary temporary name for use in C.""" - return f'c_{self.name}2' - - def tmp_counter(self): - """Get a temporary counter variable to be used in a loop.""" - name = f'{self.name}_i_{self.used_counters}' - self.used_counters += 1 - return name - - @abstractmethod - def declare(self): - """Return a declaration for the type.""" - - def declare_tmp(self): - """Declare temporaries on in the subroutine.""" - return [] - - def declare_cbinding_fortran(self): - """Return the C binding declaration as seen from Fortran.""" - return self.declare() - - def argument(self): - """Return the value to pass as an argument.""" - return self.name - - def use(self): - """Return list of (module, name) for a Fortran use-statement.""" - return [] - - def post(self): - """Return post-processing code to be run after the call.""" - return [] - - @abstractmethod - def c_parameter(self): - """Return the parameter expression to be used in the C function.""" - - def c_declare_tmp(self): - """Code to declare temporary variables for conversions, etc..""" - return [] - - def c_shortcut_condition(self): - """Shortcut conditional code. - - If the conditional evaluates to true in C, then code defined in - c_shortcut_code() for all other parameters will be run and the - underlying C function will not be called. - """ - return None - - def c_shortcut_code(self): - """Shortcut code to run if a parameter defines a shortcut condition.""" - return [] - - def c_prepare(self): - """Code to be called before being passed to underlying C function.""" - return [] - - def c_argument(self): - """Return the value to pass as an argument in the C code.""" - return self.name - - def c_post(self): - """Code to be run after a call to the underlying C function.""" - return [] - - -# -# Definitions of generic types in Fortran and how these can be converted -# to and from C. -# - - -@FortranType.add('BUFFER') -class BufferType(FortranType): - def declare(self): - return f'OMPI_FORTRAN_IGNORE_TKR_TYPE, INTENT(IN) :: {self.name}' - - def c_parameter(self): - return f'char *{self.name}' - - def c_argument(self): - return f'OMPI_F2C_BOTTOM({self.name})' - - -@FortranType.add('BUFFER_ASYNC') -class BufferAsyncType(BufferType): - def declare(self): - return f'OMPI_FORTRAN_IGNORE_TKR_TYPE, INTENT(IN) OMPI_ASYNCHRONOUS :: {self.name}' - - -@FortranType.add('BUFFER_OUT') -class BufferOutType(BufferType): - def declare(self): - return f'OMPI_FORTRAN_IGNORE_TKR_TYPE :: {self.name}' - - -@FortranType.add('BUFFER_ASYNC_OUT') -class BufferAsyncOutType(BufferType): - def declare(self): - return f'OMPI_FORTRAN_IGNORE_TKR_TYPE OMPI_ASYNCHRONOUS :: {self.name}' - - -@FortranType.add('COUNT') -class CountType(FortranType): - def declare(self): - if self.bigcount: - return f'INTEGER(KIND=MPI_COUNT_KIND), INTENT(IN) :: {self.name}' - else: - return f'INTEGER, INTENT(IN) :: {self.name}' - - def use(self): - return [('mpi_f08_types', 'MPI_COUNT_KIND')] - - def c_parameter(self): - type_ = 'MPI_Count' if self.bigcount else 'MPI_Fint' - return f'{type_} *{self.name}' - - def c_argument(self): - return f'*{self.name}' if self.bigcount else f'OMPI_FINT_2_INT(*{self.name})' - - -@FortranType.add('DATATYPE') -class DatatypeType(FortranType): - def declare(self): - return f'TYPE(MPI_Datatype), INTENT(IN) :: {self.name}' - - def declare_cbinding_fortran(self): - return f'INTEGER, INTENT(IN) :: {self.name}' - - def argument(self): - return f'{self.name}%MPI_VAL' - - def use(self): - return [('mpi_f08_types', 'MPI_Datatype')] - - def c_parameter(self): - return f'MPI_Fint *{self.name}' - - def c_prepare(self): - return [f'MPI_Datatype {self.tmp_name} = PMPI_Type_f2c(*{self.name});'] - - def c_argument(self): - return self.tmp_name - - -@FortranType.add('INT') -class IntType(FortranType): - def declare(self): - return f'INTEGER, INTENT(IN) :: {self.name}' - - def c_parameter(self): - return f'MPI_Fint *{self.name}' - - def c_argument(self): - return f'OMPI_FINT_2_INT(*{self.name})' - - -@FortranType.add('RANK') -class RankType(IntType): - pass - - -@FortranType.add('TAG') -class TagType(IntType): - pass - - -@FortranType.add('INDEX_OUT') -class IndexOutType(IntType): - def declare(self): - return f'INTEGER, INTENT(OUT) :: {self.name}' - - def c_declare_tmp(self): - return [f'int {self.tmp_name};'] - - def c_shortcut_code(self): - return [f'*{self.name} = OMPI_INT_2_FINT(MPI_UNDEFINED);'] - - def c_argument(self): - return f'&{self.tmp_name}' - - def c_post(self): - return [ - f'if (MPI_SUCCESS == {C_ERROR_TMP_NAME} && MPI_UNDEFINED != {self.tmp_name}) {{', - f' {self.tmp_name} += 1;' - f' *{self.name} = OMPI_INT_2_FINT({self.tmp_name});' - '}', - ] - - -@FortranType.add('LOGICAL_OUT') -class LogicalOutType(IntType): - """Logical type. - - NOTE: Since the logical type causes difficulties when passed to C code, - this code uses a temporary integer in Fortran to pass to the C code. On - completion the logical type is set based on C's true/false rules. - """ - - def declare(self): - return f'LOGICAL, INTENT(OUT) :: {self.name}' - - def declare_tmp(self): - return [f'INTEGER :: {self.tmp_name} = 0'] - - def declare_cbinding_fortran(self): - return f'INTEGER, INTENT(OUT) :: {self.name}' - - def argument(self): - return self.tmp_name - - def post(self): - return [f'{self.name} = {self.tmp_name} /= 0'] - - def c_parameter(self): - return f'MPI_Fint *{self.name}' - - def c_declare_tmp(self): - return [f'int {self.tmp_name};'] - - def c_shortcut_code(self): - return [f'*{self.name} = OMPI_INT_2_FINT(1);'] - - def c_argument(self): - return f'&{self.tmp_name}' - - def c_post(self): - return [f'*{self.name} = OMPI_INT_2_FINT({self.tmp_name});'] - - -@FortranType.add('COMM') -class CommType(FortranType): - def declare(self): - return f'TYPE(MPI_Comm), INTENT(IN) :: {self.name}' - - def declare_cbinding_fortran(self): - return f'INTEGER, INTENT(IN) :: {self.name}' - - def argument(self): - return f'{self.name}%MPI_VAL' - - def use(self): - return [('mpi_f08_types', 'MPI_Comm')] - - def c_parameter(self): - return f'MPI_Fint *{self.name}' - - def c_prepare(self): - return [f'MPI_Comm {self.tmp_name} = PMPI_Comm_f2c(*{self.name});'] - - def c_argument(self): - return self.tmp_name - - -@FortranType.add('STATUS') -class StatusType(FortranType): - def declare(self): - return f'TYPE(MPI_Status), INTENT(OUT) :: {self.name}' - - def use(self): - return [('mpi_f08_types', 'MPI_Status')] - - def c_parameter(self): - # TODO: Is this correct? (I've listed it as TYPE(MPI_Status) in the binding) - return f'MPI_Fint *{self.name}' - - def c_shortcut_code(self): - return [f'PMPI_Status_c2f(&ompi_status_empty, {self.name});'] - - # TODO: This code should be in c_declare_tmp() - def c_prepare(self): - return [ - f'OMPI_FORTRAN_STATUS_DECLARATION({self.tmp_name}, {self.tmp_name2});', - f'OMPI_FORTRAN_STATUS_SET_POINTER({self.tmp_name}, {self.tmp_name2}, {self.name});' - ] - - def c_argument(self): - return self.tmp_name - - def c_post(self): - return [f'OMPI_FORTRAN_STATUS_RETURN({self.tmp_name}, {self.tmp_name2}, {self.name}, {C_ERROR_TMP_NAME});'] - - -@FortranType.add('SHORTCUT_COUNT') -class ShortcutCountType(FortranType): - """Shortcut count type. - - This type is an integer that, when 0, can be used to shortcut a call to the - underyling C binding. Other types may implement a `c_shortcut` method that - will return code to execute upon a shortcut operation. - - The shortcut conditional is placed right after c temporary declarations but - before the c prepare code. - """ - - def declare(self): - return f'INTEGER, INTENT(IN) :: {self.name}' - - def c_parameter(self): - return f'MPI_Fint *{self.name}' - - def c_shortcut_condition(self): - return f'OPAL_UNLIKELY(0 == OMPI_FINT_2_INT(*{self.name}))' - - def c_argument(self): - return f'OMPI_FINT_2_INT(*{self.name})' - - -@FortranType.add('REQUEST') -class RequestType(FortranType): - def declare(self): - return f'TYPE(MPI_Request), INTENT(OUT) :: {self.name}' - - def declare_cbinding_fortran(self): - return f'INTEGER, INTENT(OUT) :: {self.name}' - - def argument(self): - return f'{self.name}%MPI_VAL' - - def use(self): - return [('mpi_f08_types', 'MPI_Request')] - - def c_parameter(self): - return f'MPI_Fint *{self.name}' - - def c_declare_tmp(self): - return [f'MPI_Request {self.tmp_name};'] - - def c_argument(self): - return f'&{self.tmp_name}' - - def c_post(self): - return [ - f'if (MPI_SUCCESS == {C_ERROR_TMP_NAME}) {{', - f' *{self.name} = PMPI_Request_c2f({self.tmp_name});', - '}', - ] - - -def allocate_array(name, malloc_expr, fn_api_name): - """Generate code for allocating an array and checking the result.""" - return [ - f'{name} = malloc({malloc_expr});', - f'if (NULL == {name}) {{', - f' {C_ERROR_TMP_NAME} = OMPI_ERRHANDLER_NOHANDLE_INVOKE(MPI_ERR_NO_MEM, "{fn_api_name}");', - f' *{C_ERROR_NAME} = OMPI_INT_2_FINT({C_ERROR_TMP_NAME});', - ' return;', - '}', - ] - - -@FortranType.add('REQUEST_ARRAY') -class RequestArrayType(FortranType): - def declare(self): - return f'TYPE(MPI_Request), INTENT(INOUT) :: {self.name}({self.dep_param.name})' - - def declare_cbinding_fortran(self): - return f'INTEGER, INTENT(INOUT) :: {self.name}({self.dep_param.name})' - - def argument(self): - return f'{self.name}(:)%MPI_VAL' - - def use(self): - return [('mpi_f08_types', 'MPI_Request')] - - def c_parameter(self): - return f'MPI_Fint *{self.name}' - - def c_declare_tmp(self): - return [f'MPI_Request *{self.tmp_name};'] - - def c_prepare(self): - tmp_name = self.tmp_name - code = allocate_array(tmp_name, - f'{self.dep_param.c_argument()} * sizeof(MPI_Request)', - self.fn_api_name) - i = self.tmp_counter() - code.extend([ - f'for (int {i} = 0; {i} < {self.dep_param.c_argument()}; ++{i}) {{', - f' {tmp_name}[{i}] = PMPI_Request_f2c({self.name}[{i}]);', - '}', - ]) - return code - - def c_argument(self): - return self.tmp_name - - def c_post(self): - i = self.tmp_counter() - return [ - f'if (MPI_SUCCESS == {C_ERROR_TMP_NAME}) {{', - f' for (int {i} = 0; {i} < {self.dep_param.c_argument()}; ++{i}) {{', - f' {self.name}[{i}] = {self.tmp_name}[{i}]->req_f_to_c_index;', - ' }', - '}', - f'free({self.tmp_name});', - ] - - -@FortranType.add('STATUS_ARRAY') -class StatusArrayType(FortranType): - def declare(self): - return f'TYPE(MPI_Status), INTENT(OUT) :: {self.name}(*)' - - def use(self): - return [('mpi_f08_types', 'MPI_Status')] - - def c_parameter(self): - return f'MPI_Fint *{self.name}' - - def c_declare_tmp(self): - return [f'MPI_Status *{self.tmp_name};'] - - def c_prepare(self): - return allocate_array(self.tmp_name, - f'{self.dep_param.c_argument()} * sizeof(MPI_Status)', - self.fn_api_name) - - def c_argument(self): - return self.tmp_name - - def c_post(self): - i = self.tmp_counter() - return [ - f'if (MPI_SUCCESS == {C_ERROR_TMP_NAME}) {{', - f' for (int {i} = 0; {i} < {self.dep_param.c_argument()}; ++{i}) {{', - f' if (!OMPI_IS_FORTRAN_STATUSES_IGNORE({self.name}) &&', - f' !OMPI_IS_FORTRAN_STATUS_IGNORE(&{self.name}[{i}])) {{', - f' PMPI_Status_c2f(&{self.tmp_name}[{i}], &{self.name}[{i} * (sizeof(MPI_Status) / sizeof(int))]);', - ' }', - ' }', - '}', - f'free({self.tmp_name});' - ] - - -class PrototypeParseError(Exception): - """Thrown when a parsing error is encountered.""" - - -def c_api_func_name(fn_name, bigcount=False): - """Produce the actual MPI API function name to call into.""" - suffix = '_c' if bigcount else '' - return f'MPI_{fn_name.capitalize()}{suffix}' - - -def c_api_func_name_profile(fn_name, bigcount=False): - """Produce the actual PMPI API function name to call into.""" - return f'P{c_api_func_name(fn_name, bigcount)}' - - -def fortran_f08_name(fn_name, bigcount=False): - """Produce the final f08 name from base_name.""" - suffix = '_c' if bigcount else '' - return f'MPI_{fn_name.capitalize()}_f08{suffix}' - - -FortranParameter = namedtuple('FortranParameter', ['type_', 'name', 'dep_name']) -FortranPrototype = namedtuple('FortranPrototype', ['fn_name', 'parameters']) - - -def load_prototypes(fname): - """Load the prototypes from a file.""" - with open(fname) as fp: - prototypes = [] - for i, line in enumerate(fp): - lno = i + 1 - line = line.strip() - if line and line[0] == '#': - continue - if PROTOTYPE_RE.match(line) is None: - raise PrototypeParseError(f'Invalid function prototype for Fortran interface on line {lno}') - start = line.index('(') - end = line.index(')') - fn_name = line[:start].strip() - parameters = line[start+1:end].split(',') - parsed_parameters = [] - for param in parameters: - param = param.strip() - type_, name = param.split() - dep_name = None - # Check for 'param:other_param' parameters, indicating a - # dependency on that other parameter (such as for a count) - if ':' in name: - name, dep_name = name.split(':') - parsed_parameters.append(FortranParameter(type_, name, dep_name)) - prototypes.append(FortranPrototype(fn_name, parsed_parameters)) - return prototypes - - -class FortranBinding: - """Class for generating the binding for a single function.""" - - def __init__(self, prototype, bigcount=False): - self.bigcount = bigcount - self.fn_name = prototype.fn_name - self.parameters = [] - param_map = {} - dep_params = {} - for param in prototype.parameters: - type_ = FortranType.get(param.type_) - param_type = type_(param.name, self.fn_name, bigcount=bigcount) - self.parameters.append(param_type) - param_map[param.name] = param_type - if param.dep_name is not None: - dep_params[param.name] = param.dep_name - # Set dependent parameters for those that need them - for name, dep_name in dep_params.items(): - param_map[name].dep_param = param_map[dep_name] - - def _fn_name_suffix(self): - """Return a suffix for function names.""" - return '_c' if self.bigcount else '' - - @property - def c_func_name(self): - """Produce the final C func name from base_name.""" - return f'ompi_{self.fn_name}_wrapper_f08{self._fn_name_suffix()}' - - def _param_list(self): - return ','.join(type_.name for type_ in self.parameters) - - def _use(self): - """Determine the Fortran use-statements needed.""" - use = {} - for param in self.parameters: - for mod, name in param.use(): - if mod not in use: - use[mod] = set() - use[mod].add(name) - return use - - def _use_stmts(self): - """Return a list of required use statments.""" - use = self._use() - stmts = [] - for mod, names in use.items(): - names = ', '.join(names) - stmts.append(f'use :: {mod}, only: {names}') - return stmts - - def _print_fortran_interface(self): - """Output the C subroutine binding for the Fortran code.""" - name = self.c_func_name - print(' interface') - print(f' subroutine {name}({self._param_list()},{FORTRAN_ERROR_NAME}) &') - print(f' BIND(C, name="{name}")') - use_stmts = self._use_stmts() - for stmt in use_stmts: - print(f' {stmt}') - print(' implicit none') - for param in self.parameters: - print(f' {param.declare_cbinding_fortran()}') - print(f' INTEGER, INTENT(OUT) :: {FORTRAN_ERROR_NAME}') - print(f' end subroutine {name}') - print(' end interface') - - def print_f_source(self): - """Output the main MPI Fortran subroutine.""" - sub_name = fortran_f08_name(self.fn_name, bigcount=self.bigcount) - c_func = self.c_func_name - print('subroutine', f'{sub_name}({self._param_list()},{FORTRAN_ERROR_NAME})') - # Use statements - use_stmts = self._use_stmts() - for stmt in use_stmts: - print(f' {stmt}') - print(' implicit none') - # Parameters/dummy variable declarations - types = [] - for param in self.parameters: - print(f' {param.declare()}') - # Add the integer error manually - print(f' INTEGER, OPTIONAL, INTENT(OUT) :: {FORTRAN_ERROR_NAME}') - # Temporaries - print(f' INTEGER :: {C_ERROR_TMP_NAME}') - for param in self.parameters: - for line in param.declare_tmp(): - print(f' {line}') - - # Interface for call to C function - print() - self._print_fortran_interface() - print() - - # Call into the C function - args = ','.join(param.argument() for param in self.parameters) - print(f' call {c_func}({args},{C_ERROR_TMP_NAME})') - # Convert error type - print(f' if (present({FORTRAN_ERROR_NAME})) {FORTRAN_ERROR_NAME} = {C_ERROR_TMP_NAME}') - - for param in self.parameters: - for line in param.post(): - print(f' {line}') - - print(f'end subroutine {sub_name}') - - def print_c_source(self): - """Output the C source and function that the Fortran calls into.""" - parameters = [param.c_parameter() for param in self.parameters] - # Always append the integer error - parameters.append(f'MPI_Fint *{C_ERROR_NAME}') - parameters = ', '.join(parameters) - # Just put the signature here to silence `-Wmissing-prototypes` - c_func = self.c_func_name - print(f'void {c_func}({parameters});') - print(f'void {c_func}({parameters})') - print('{') - print(f' int {C_ERROR_TMP_NAME}; ') - - # First the temporary declarations - for param in self.parameters: - for line in param.c_declare_tmp(): - print(f' {line}') - - # Shortcut conditions, if any - for param in self.parameters: - condition = param.c_shortcut_condition() - if condition is None: - continue - print(f' if ({condition}) {{') - print(f' *{C_ERROR_NAME} = OMPI_INT_2_FINT(MPI_SUCCESS);') - for other_param in self.parameters: - for line in other_param.c_shortcut_code(): - print(f' {line}') - print(' return;') - print(' }') - - # Prepare code for temporaries, etc. - for param in self.parameters: - for line in param.c_prepare(): - print(f' {line}') - - # Call into the C API - c_api_func = c_api_func_name_profile(self.fn_name, bigcount=self.bigcount) - arguments = [param.c_argument() for param in self.parameters] - arguments = ', '.join(arguments) - print(f' {C_ERROR_TMP_NAME} = {c_api_func}({arguments});') - - # Post-processing code - print(f' *{C_ERROR_NAME} = OMPI_INT_2_FINT({C_ERROR_TMP_NAME});') - for param in self.parameters: - for line in param.c_post(): - print(f' {line}') - print('}') - - -def print_f_source_header(): - """Print the fortran f08 file header.""" - print(f'! {GENERATED_MESSAGE}') - print('#include "ompi/mpi/fortran/configure-fortran-output.h"') - - -def print_profiling_rename_macros(prototypes): - """Print macros for renaming functions for the profiling interface. - - Previously hardcoded in mpi-f08-rename.h. - """ - print('#if OMPI_BUILD_MPI_PROFILING') - for prototype in prototypes: - name = fortran_f08_name(prototype.fn_name) - print(f'#define {name} P{name}') - # Check for bigcount version - if any(param.type_ == 'COUNT' for param in prototype.parameters): - bigcount_name = fortran_f08_name(prototype.fn_name, bigcount=True) - print(f'#define {bigcount_name} P{bigcount_name}') - print('#endif /* OMPI_BUILD_MPI_PROFILING */') - - -def print_c_source_header(): - """Print the header of the C source file.""" - print(f'/* {GENERATED_MESSAGE} */') - print('#include "ompi_config.h"') - print('#include "mpi.h"') - print('#include "ompi/errhandler/errhandler.h"') - print('#include "ompi/mpi/fortran/mpif-h/status-conversion.h"') - print('#include "ompi/mpi/fortran/base/constants.h"') - print('#include "ompi/mpi/fortran/base/fint_2_int.h"') - print('#include "ompi/request/request.h"') - - -def print_binding(prototype, lang, bigcount=False): - """Print the binding with or without bigcount.""" - binding = FortranBinding(prototype, bigcount=bigcount) - if lang == 'fortran': - binding.print_f_source() - else: - binding.print_c_source() - - -def main(): - parser = argparse.ArgumentParser(description='generate fortran binding files') - parser.add_argument('lang', choices=('fortran', 'c'), - help='generate dependent files in C or Fortran') - parser.add_argument('template', help='template file to use') - parser.add_argument('--bigcount', action='store_true', - help='generate bigcount interface for function') - args = parser.parse_args() - - prototypes = load_prototypes(args.template) - if args.lang == 'fortran': - print_f_source_header() - print() - print_profiling_rename_macros(prototypes) - print() - else: - print_c_source_header() - for prototype in prototypes: - print() - print_binding(prototype, args.lang) - if any(param.type_ == 'COUNT' for param in prototype.parameters): - print() - print_binding(prototype, args.lang, bigcount=True) - - -if __name__ == '__main__': - main() diff --git a/ompi/mpi/fortran/use-mpi-f08/interface.in b/ompi/mpi/fortran/use-mpi-f08/interface.in index 3593fa81c8c..e47168e11fc 100644 --- a/ompi/mpi/fortran/use-mpi-f08/interface.in +++ b/ompi/mpi/fortran/use-mpi-f08/interface.in @@ -11,10 +11,33 @@ # to generate Fortran subroutines and internal C functions that wrap the C MPI # interface versions of each function. # -recv(BUFFER_OUT buf, COUNT count, DATATYPE datatype, RANK source, TAG tag, COMM comm, STATUS status) -irecv(BUFFER_ASYNC_OUT buf, COUNT count, DATATYPE datatype, RANK source, TAG tag, COMM comm, REQUEST request) -send(BUFFER buf, COUNT count, DATATYPE datatype, RANK dest, TAG tag, COMM comm) -isend(BUFFER_ASYNC buf, COUNT count, DATATYPE data, RANK dest, TAG tag, COMM comm, REQUEST request) -waitall(SHORTCUT_COUNT count, REQUEST_ARRAY array_of_requests:count, STATUS_ARRAY array_of_statuses:count) -testany(SHORTCUT_COUNT count, REQUEST_ARRAY array_of_requests:count, INDEX_OUT index, LOGICAL_OUT flag, STATUS status) -alltoall(BUFFER sendbuf, INT sendcount, DATATYPE sendtype, BUFFER recvbuf, INT recvcount, DATATYPE recvtype, COMM comm) +# Parameters of the form 'TYPE param_name[key=other_param_name]' allow for +# dependencies between different parameters (this is useful for buffer +# parameters). +.recv(BUFFER_OUT buf[count=count;type=datatype;comm=comm], COUNT count, + DATATYPE datatype, RANK source, TAG tag, COMM comm, STATUS status) +.irecv(BUFFER_ASYNC_OUT buf[count=count;type=datatype;comm=comm], COUNT count, + DATATYPE datatype, RANK source, TAG tag, COMM comm, REQUEST request) +.send(BUFFER buf[count=count;type=datatype;comm=comm], COUNT count, + DATATYPE datatype, RANK dest, TAG tag, COMM comm) +.isend(BUFFER_ASYNC buf[count=count;type=datatype;comm=comm], COUNT count, + DATATYPE datatype, RANK dest, TAG tag, COMM comm, REQUEST request) +.waitall(SHORTCUT_COUNT count, REQUEST_ARRAY array_of_requests[count=count], + STATUS_ARRAY array_of_statuses[count=count]) +.testany(SHORTCUT_COUNT count, REQUEST_ARRAY array_of_requests[count=count], + INDEX_OUT index, LOGICAL_OUT flag, STATUS status) +.alltoall(BUFFER sendbuf[count=sendcount;type=sendtype;comm=comm], + INT sendcount, DATATYPE sendtype, + BUFFER recvbuf[count=recvcount;type=recvtype;comm=comm], + INT recvcount, DATATYPE recvtype, COMM comm) +.alltoallv(VBUFFER sendbuf[counts=sendcounts;displs=sdispls;type=sendtype;comm=comm], + COUNT_ARRAY sendcounts[comm=comm], DISPL_ARRAY sdispls[comm=comm], + DATATYPE sendtype, VBUFFER_OUT recvbuf[comm=comm;type=recvtype], + COUNT_ARRAY recvcounts[comm=comm], DISPL_ARRAY rdispls[comm=comm], + DATATYPE recvtype, COMM comm) +.alltoallw(WBUFFER sendbuf[counts=sendcounts;displs=sdispls;types=sendtypes;comm=comm], + COUNT_ARRAY sendcounts[comm=comm], DISPL_ARRAY sdispls[comm=comm], + DATATYPE_ARRAY sendtypes, + WBUFFER_OUT recvbuf[counts=recvcounts;displs=rdispls;types=recvtypes;comm=comm], + COUNT_ARRAY recvcounts[comm=comm], DISPL_ARRAY rdispls[comm=comm], + DATATYPE_ARRAY recvtypes, COMM comm) diff --git a/ompi/mpi/fortran/use-mpi-f08/mod/Makefile.am b/ompi/mpi/fortran/use-mpi-f08/mod/Makefile.am index 87f1c2db8ba..be8d16777eb 100644 --- a/ompi/mpi/fortran/use-mpi-f08/mod/Makefile.am +++ b/ompi/mpi/fortran/use-mpi-f08/mod/Makefile.am @@ -10,6 +10,8 @@ # Copyright (c) 2015-2020 Research Organization for Information Science # and Technology (RIST). All rights reserved. # Copyright (c) 2016 IBM Corporation. All rights reserved. +# Copyright (C) 2024 Triad National Security, LLC. All rights +# reserved. # # $COPYRIGHT$ # @@ -62,7 +64,9 @@ libforce_usempif08_internal_modules_to_be_built_la_SOURCES = \ pmpi-f08-interfaces.F90 \ mpi-f08-interfaces-callbacks.F90 -nodist_noinst_HEADERS = mpi-f08-interfaces.h +nodist_noinst_HEADERS = \ + mpi-f08-interfaces.h \ + mpi-f08-interfaces-generated.h noinst_HEADERS = mpi-f08-rename.h @@ -72,6 +76,20 @@ config_h = \ $(top_builddir)/ompi/mpi/fortran/configure-fortran-output.h \ $(top_srcdir)/ompi/mpi/fortran/configure-fortran-output-bottom.h +DISTCLEANFILES = mpi-f08-interfaces-generated.h + +# Generate the Fortran interfaces +if OMPI_GENERATE_BINDINGS +mpi-f08-interfaces-generated.h: ../interface.in + $(PYTHON) $(top_srcdir)/ompi/mpi/bindings/bindings.py \ + --builddir $(abs_top_builddir) \ + --srcdir $(abs_top_srcdir) \ + --output $(abs_builddir)/$@ \ + fortran \ + --template $(abs_srcdir)/$< \ + interface +endif + # # Automake doesn't do Fortran dependency analysis, so must list them # manually here. Bummer! @@ -83,6 +101,7 @@ mpi-f08-interfaces.lo: $(config_h) mpi-f08-interfaces.lo: mpi-f08-interfaces.F90 mpi-f08-interfaces.lo: mpi-f08-interfaces-callbacks.lo mpi-f08-interfaces.lo: mpi-f08-interfaces.h +mpi-f08-interfaces.lo: mpi-f08-interfaces-generated.h mpi-f08-interfaces-callbacks.lo: $(config_h) mpi-f08-interfaces-callbacks.lo: mpi-f08-interfaces-callbacks.F90 mpi-f08-interfaces-callbacks.lo: mpi-f08-types.lo @@ -94,6 +113,7 @@ pmpi-f08-interfaces.lo: pmpi-f08-interfaces.F90 pmpi-f08-interfaces.lo: mpi-f08-interfaces-callbacks.lo pmpi-f08-interfaces.lo: mpi-f08-interfaces.h pmpi-f08-interfaces.lo: mpi-f08-rename.h +pmpi-f08-interfaces.lo: mpi-f08-interfaces-generated.h ########################################################################### diff --git a/ompi/mpi/fortran/use-mpi-f08/mod/mpi-f08-interfaces.F90 b/ompi/mpi/fortran/use-mpi-f08/mod/mpi-f08-interfaces.F90 index 71cefb1f128..ad4a92223b0 100644 --- a/ompi/mpi/fortran/use-mpi-f08/mod/mpi-f08-interfaces.F90 +++ b/ompi/mpi/fortran/use-mpi-f08/mod/mpi-f08-interfaces.F90 @@ -24,6 +24,7 @@ module mpi_f08_interfaces #include "mpi-f08-interfaces.h" +#include "mpi-f08-interfaces-generated.h" ! MPI_Wtick is not a wrapper function ! diff --git a/ompi/mpi/fortran/use-mpi-f08/mod/mpi-f08-interfaces.h.in b/ompi/mpi/fortran/use-mpi-f08/mod/mpi-f08-interfaces.h.in index 578b7cb3709..d694247bbc4 100644 --- a/ompi/mpi/fortran/use-mpi-f08/mod/mpi-f08-interfaces.h.in +++ b/ompi/mpi/fortran/use-mpi-f08/mod/mpi-f08-interfaces.h.in @@ -112,20 +112,6 @@ subroutine MPI_Iprobe_f08(source,tag,comm,flag,status,ierror) end subroutine MPI_Iprobe_f08 end interface MPI_Iprobe -interface MPI_Irecv -subroutine MPI_Irecv_f08(buf,count,datatype,source,tag,comm,request,ierror) - use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm, MPI_Request - implicit none - @OMPI_FORTRAN_IGNORE_TKR_PREDECL@ buf - @OMPI_FORTRAN_IGNORE_TKR_TYPE@ OMPI_ASYNCHRONOUS :: buf - INTEGER, INTENT(IN) :: count, source, tag - TYPE(MPI_Datatype), INTENT(IN) :: datatype - TYPE(MPI_Comm), INTENT(IN) :: comm - TYPE(MPI_Request), INTENT(OUT) :: request - INTEGER, OPTIONAL, INTENT(OUT) :: ierror -end subroutine MPI_Irecv_f08 -end interface MPI_Irecv - interface MPI_Irsend subroutine MPI_Irsend_f08(buf,count,datatype,dest,tag,comm,request,ierror) use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm, MPI_Request @@ -140,20 +126,6 @@ subroutine MPI_Irsend_f08(buf,count,datatype,dest,tag,comm,request,ierror) end subroutine MPI_Irsend_f08 end interface MPI_Irsend -interface MPI_Isend -subroutine MPI_Isend_f08(buf,count,datatype,dest,tag,comm,request,ierror) - use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm, MPI_Request - implicit none - @OMPI_FORTRAN_IGNORE_TKR_PREDECL@ buf - @OMPI_FORTRAN_IGNORE_TKR_TYPE@, INTENT(IN) OMPI_ASYNCHRONOUS :: buf - INTEGER, INTENT(IN) :: count, dest, tag - TYPE(MPI_Datatype), INTENT(IN) :: datatype - TYPE(MPI_Comm), INTENT(IN) :: comm - TYPE(MPI_Request), INTENT(OUT) :: request - INTEGER, OPTIONAL, INTENT(OUT) :: ierror -end subroutine MPI_Isend_f08 -end interface MPI_Isend - interface MPI_Isendrecv subroutine MPI_Isendrecv_f08(sendbuf,sendcount,sendtype,dest,sendtag,recvbuf, & recvcount,recvtype,source,recvtag,comm,request,ierror) @@ -290,32 +262,6 @@ subroutine MPI_Probe_f08(source,tag,comm,status,ierror) end subroutine MPI_Probe_f08 end interface MPI_Probe -interface MPI_Recv -subroutine MPI_Recv_f08(buf,count,datatype,source,tag,comm,status,ierror) - use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm, MPI_Status - implicit none - @OMPI_FORTRAN_IGNORE_TKR_PREDECL@ buf - @OMPI_FORTRAN_IGNORE_TKR_TYPE@ :: buf - INTEGER, INTENT(IN) :: count, source, tag - TYPE(MPI_Datatype), INTENT(IN) :: datatype - TYPE(MPI_Comm), INTENT(IN) :: comm - TYPE(MPI_Status) :: status - INTEGER, OPTIONAL, INTENT(OUT) :: ierror -end subroutine MPI_Recv_f08 -subroutine MPI_Recv_f08_c(buf,count,datatype,source,tag,comm,status,ierror) - use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm, MPI_Status, MPI_COUNT_KIND - implicit none - @OMPI_FORTRAN_IGNORE_TKR_PREDECL@ buf - @OMPI_FORTRAN_IGNORE_TKR_TYPE@ :: buf - INTEGER(KIND=MPI_COUNT_KIND), INTENT(IN) :: count - INTEGER, INTENT(IN) :: source, tag - TYPE(MPI_Datatype), INTENT(IN) :: datatype - TYPE(MPI_Comm), INTENT(IN) :: comm - TYPE(MPI_Status) :: status - INTEGER, OPTIONAL, INTENT(OUT) :: ierror -end subroutine MPI_Recv_f08_c -end interface MPI_Recv - interface MPI_Recv_init subroutine MPI_Recv_init_f08(buf,count,datatype,source,tag,comm,request,ierror) use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm, MPI_Request @@ -377,30 +323,6 @@ subroutine MPI_Rsend_init_f08(buf,count,datatype,dest,tag,comm,request,ierror) end subroutine MPI_Rsend_init_f08 end interface MPI_Rsend_init -interface MPI_Send -subroutine MPI_Send_f08(buf,count,datatype,dest,tag,comm,ierror) - use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm - implicit none - @OMPI_FORTRAN_IGNORE_TKR_PREDECL@ buf - @OMPI_FORTRAN_IGNORE_TKR_TYPE@, INTENT(IN) :: buf - INTEGER, INTENT(IN) :: count, dest, tag - TYPE(MPI_Datatype), INTENT(IN) :: datatype - TYPE(MPI_Comm), INTENT(IN) :: comm - INTEGER, OPTIONAL, INTENT(OUT) :: ierror -end subroutine MPI_Send_f08 -subroutine MPI_Send_f08_c(buf,count,datatype,dest,tag,comm,ierror) - use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm, MPI_COUNT_KIND - implicit none - @OMPI_FORTRAN_IGNORE_TKR_PREDECL@ buf - @OMPI_FORTRAN_IGNORE_TKR_TYPE@, INTENT(IN) :: buf - INTEGER(KIND=MPI_COUNT_KIND), INTENT(IN) :: count - INTEGER, INTENT(IN) :: dest, tag - TYPE(MPI_Datatype), INTENT(IN) :: datatype - TYPE(MPI_Comm), INTENT(IN) :: comm - INTEGER, OPTIONAL, INTENT(OUT) :: ierror -end subroutine MPI_Send_f08_c -end interface MPI_Send - interface MPI_Sendrecv subroutine MPI_Sendrecv_f08(sendbuf,sendcount,sendtype,dest,sendtag,recvbuf, & recvcount,recvtype,source,recvtag,comm,status,ierror) @@ -621,19 +543,6 @@ subroutine MPI_Testall_f08(count,array_of_requests,flag,array_of_statuses,ierror end subroutine MPI_Testall_f08 end interface MPI_Testall -interface MPI_Testany -subroutine MPI_Testany_f08(count,array_of_requests,index,flag,status,ierror) - use :: mpi_f08_types, only : MPI_Request, MPI_Status - implicit none - INTEGER, INTENT(IN) :: count - TYPE(MPI_Request), INTENT(INOUT) :: array_of_requests(count) - INTEGER, INTENT(OUT) :: index - LOGICAL, INTENT(OUT) :: flag - TYPE(MPI_Status) :: status - INTEGER, OPTIONAL, INTENT(OUT) :: ierror -end subroutine MPI_Testany_f08 -end interface MPI_Testany - interface MPI_Testsome subroutine MPI_Testsome_f08(incount,array_of_requests,outcount, & array_of_indices,array_of_statuses,ierror) @@ -667,17 +576,6 @@ subroutine MPI_Wait_f08(request,status,ierror) end subroutine MPI_Wait_f08 end interface MPI_Wait -interface MPI_Waitall -subroutine MPI_Waitall_f08(count,array_of_requests,array_of_statuses,ierror) - use :: mpi_f08_types, only : MPI_Request, MPI_Status - implicit none - INTEGER, INTENT(IN) :: count - TYPE(MPI_Request), INTENT(INOUT) :: array_of_requests(count) - TYPE(MPI_Status) :: array_of_statuses(*) - INTEGER, OPTIONAL, INTENT(OUT) :: ierror -end subroutine MPI_Waitall_f08 -end interface MPI_Waitall - interface MPI_Waitany subroutine MPI_Waitany_f08(count,array_of_requests,index,status,ierror) use :: mpi_f08_types, only : MPI_Request, MPI_Status @@ -1229,21 +1127,6 @@ subroutine MPI_Allreduce_init_f08(sendbuf,recvbuf,count,datatype,op,comm,info,re end subroutine MPI_Allreduce_init_f08 end interface MPI_Allreduce_init -interface MPI_Alltoall -subroutine MPI_Alltoall_f08(sendbuf,sendcount,sendtype,recvbuf,recvcount,recvtype, & - comm,ierror) - use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm - implicit none - @OMPI_FORTRAN_IGNORE_TKR_PREDECL@ sendbuf, recvbuf - @OMPI_FORTRAN_IGNORE_TKR_TYPE@, INTENT(IN) :: sendbuf - @OMPI_FORTRAN_IGNORE_TKR_TYPE@ :: recvbuf - INTEGER, INTENT(IN) :: sendcount, recvcount - TYPE(MPI_Datatype), INTENT(IN) :: sendtype, recvtype - TYPE(MPI_Comm), INTENT(IN) :: comm - INTEGER, OPTIONAL, INTENT(OUT) :: ierror -end subroutine MPI_Alltoall_f08 -end interface MPI_Alltoall - interface MPI_Ialltoall subroutine MPI_Ialltoall_f08(sendbuf,sendcount,sendtype,recvbuf,recvcount,recvtype, & comm,request,ierror) @@ -1277,21 +1160,6 @@ subroutine MPI_Alltoall_init_f08(sendbuf,sendcount,sendtype,recvbuf,recvcount,re end subroutine MPI_Alltoall_init_f08 end interface MPI_Alltoall_init -interface MPI_Alltoallv -subroutine MPI_Alltoallv_f08(sendbuf,sendcounts,sdispls,sendtype,recvbuf,recvcounts, & - rdispls,recvtype,comm,ierror) - use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm - implicit none - @OMPI_FORTRAN_IGNORE_TKR_PREDECL@ sendbuf, recvbuf - @OMPI_FORTRAN_IGNORE_TKR_TYPE@, INTENT(IN) :: sendbuf - @OMPI_FORTRAN_IGNORE_TKR_TYPE@ :: recvbuf - INTEGER, INTENT(IN) :: sendcounts(*), sdispls(*), recvcounts(*), rdispls(*) - TYPE(MPI_Datatype), INTENT(IN) :: sendtype, recvtype - TYPE(MPI_Comm), INTENT(IN) :: comm - INTEGER, OPTIONAL, INTENT(OUT) :: ierror -end subroutine MPI_Alltoallv_f08 -end interface MPI_Alltoallv - interface MPI_Ialltoallv subroutine MPI_Ialltoallv_f08(sendbuf,sendcounts,sdispls,sendtype,recvbuf,recvcounts, & rdispls,recvtype,comm,request,ierror) @@ -1325,21 +1193,6 @@ subroutine MPI_Alltoallv_init_f08(sendbuf,sendcounts,sdispls,sendtype,recvbuf,re end subroutine MPI_Alltoallv_init_f08 end interface MPI_Alltoallv_init -interface MPI_Alltoallw -subroutine MPI_Alltoallw_f08(sendbuf,sendcounts,sdispls,sendtypes,recvbuf,recvcounts, & - rdispls,recvtypes,comm,ierror) - use :: mpi_f08_types, only : MPI_Datatype, MPI_Comm - implicit none - @OMPI_FORTRAN_IGNORE_TKR_PREDECL@ sendbuf, recvbuf - @OMPI_FORTRAN_IGNORE_TKR_TYPE@, INTENT(IN) :: sendbuf - @OMPI_FORTRAN_IGNORE_TKR_TYPE@ :: recvbuf - INTEGER, INTENT(IN) :: sendcounts(*), sdispls(*), recvcounts(*), rdispls(*) - TYPE(MPI_Datatype), INTENT(IN) :: sendtypes(*), recvtypes(*) - TYPE(MPI_Comm), INTENT(IN) :: comm - INTEGER, OPTIONAL, INTENT(OUT) :: ierror -end subroutine MPI_Alltoallw_f08 -end interface MPI_Alltoallw - interface MPI_Ialltoallw subroutine MPI_Ialltoallw_f08(sendbuf,sendcounts,sdispls,sendtypes,recvbuf,recvcounts, & rdispls,recvtypes,comm,request,ierror)