Search code examples
cintmpi

How can you define the uint_fast32_t for MPI data types?


How can you define the MPI integer data type with the C/C++ uint_fast32_t or any other integer bit that uses the fast feature.


Solution

  • Let's assume we work with an MPI implementation conforming to MPI standard version 2.2 or later.

    MPI 2.2 and later define signed integer datatypes MPI_INT8_T, MPI_INT16_T, MPI_INT32_T, MPI_INT64_T (corresponding to C int8_t, int16_t, int32_t, and int64_t), and unsigned integer datatypes MPI_UINT8_T, MPI_UINT16_T, MPI_UINT32_T, and MPI_UINT64_T (corresponding to C uint8_t, uint16_t, uint32_t, and uint64_t).

    This means that you can use these specific-size integer types directly in MPI in C.


    The situation with minimum-width (int_leastN_t, uint_leastN_t) and fastest minimum-width (int_fastN_t, uint_fastN_t) integer types is different. A will tell you that you cannot really use these types with MPI, because the C or MPI standards do not provide a clean way to use them.

    In practice, the situation is much simpler. All existing C implementations supporting <stdint.h> types typedef the minimum-width and fastest minimum-width integer types to types compatible with the exact-width types.

    Personally, I would create a header file, say extra-mpi-types.h, that includes the appropriate header file, say

    /* extra_mpi_types.h */
    #ifndef   EXTRA_MPI_TYPES_H
    
    /* Use build-time generated file */
    #include <extra_mpi_types_internal.h>
    
    #endif /* EXTRA_MPI_TYPES_H */
    

    where extra_mpi_types_internal.h is generated at build time by compiling and running something like

    /* type_generator.c */
    #include <stdlib.h>
    #include <stdint.h>
    #include <stdio.h>
    
    static inline const char *mpi_type_name(const char *const name, const size_t size, const int is_signed)
    {
        if (is_signed) {
            if (size == sizeof (int8_t))  return "MPI_INT8_T";
            if (size == sizeof (int16_t)) return "MPI_INT16_T";
            if (size == sizeof (int32_t)) return "MPI_INT32_T";
            if (size == sizeof (int64_t)) return "MPI_INT64_T";
        } else {
            if (size == sizeof (uint8_t))  return "MPI_UINT8_T";
            if (size == sizeof (uint16_t)) return "MPI_UINT16_T";
            if (size == sizeof (uint32_t)) return "MPI_UINT32_T";
            if (size == sizeof (uint64_t)) return "MPI_UINT64_T";
        }
        fprintf(stderr, "%s: Unsupported %s integer type.\n", name, (is_signed) ? "signed" : "unsigned");
        exit(EXIT_FAILURE);
    }
    
    static void define(const char *const mpiname, const char *const typename, const size_t typesize, const int is_signed)
    {
        printf("#ifndef  %s\n", mpiname);
        printf("# define %s  %s\n", mpiname, mpi_type_name(typename, typesize, is_signed));
        printf("#endif\n");
    }
    
    #define  DEFINE_SIGNED(mpitype, type)    define(#mpitype, #type, sizeof (type), 1)
    #define  DEFINE_UNSIGNED(mpitype, type)  define(#mpitype, #type, sizeof (type), 0)
    
    int main(void)
    {
        printf("/* This is an autogenerated header file: do not modify. */\n\n");
    
        DEFINE_SIGNED(MPI_INT_LEAST8_T,  int_least8_t);
        DEFINE_SIGNED(MPI_INT_LEAST16_T, int_least16_t);
        DEFINE_SIGNED(MPI_INT_LEAST32_T, int_least32_t);
        DEFINE_SIGNED(MPI_INT_LEAST64_T, int_least64_t);
    
        DEFINE_UNSIGNED(MPI_UINT_LEAST8_T,  uint_least8_t);
        DEFINE_UNSIGNED(MPI_UINT_LEAST16_T, uint_least16_t);
        DEFINE_UNSIGNED(MPI_UINT_LEAST32_T, uint_least32_t);
        DEFINE_UNSIGNED(MPI_UINT_LEAST64_T, uint_least64_t);
    
        DEFINE_SIGNED(MPI_INT_FAST8_T,  int_fast8_t);
        DEFINE_SIGNED(MPI_INT_FAST16_T, int_fast16_t);
        DEFINE_SIGNED(MPI_INT_FAST32_T, int_fast32_t);
        DEFINE_SIGNED(MPI_INT_FAST64_T, int_fast64_t);
    
        DEFINE_UNSIGNED(MPI_UINT_FAST8_T,  uint_fast8_t);
        DEFINE_UNSIGNED(MPI_UINT_FAST16_T, uint_fast16_t);
        DEFINE_UNSIGNED(MPI_UINT_FAST32_T, uint_fast32_t);
        DEFINE_UNSIGNED(MPI_UINT_FAST64_T, uint_fast64_t);
    
        return EXIT_SUCCESS;
    }
    

    redirecting its output to extra_mpi_types_internal.h. Note that this depends only on the C implementation, and not on the MPI implementation at all. This only finds out which fixed-width integer types match the minimum-width or minimum-width fast integer types.

    On x86-64 Linux, this will generate

    /* This is an autogenerated header file: do not modify. */
    
    #ifndef  MPI_INT_LEAST8_T
    # define MPI_INT_LEAST8_T  MPI_INT8_T
    #endif
    #ifndef  MPI_INT_LEAST16_T
    # define MPI_INT_LEAST16_T  MPI_INT16_T
    #endif
    #ifndef  MPI_INT_LEAST32_T
    # define MPI_INT_LEAST32_T  MPI_INT32_T
    #endif
    #ifndef  MPI_INT_LEAST64_T
    # define MPI_INT_LEAST64_T  MPI_INT64_T
    #endif
    #ifndef  MPI_UINT_LEAST8_T
    # define MPI_UINT_LEAST8_T  MPI_UINT8_T
    #endif
    #ifndef  MPI_UINT_LEAST16_T
    # define MPI_UINT_LEAST16_T  MPI_UINT16_T
    #endif
    #ifndef  MPI_UINT_LEAST32_T
    # define MPI_UINT_LEAST32_T  MPI_UINT32_T
    #endif
    #ifndef  MPI_UINT_LEAST64_T
    # define MPI_UINT_LEAST64_T  MPI_UINT64_T
    #endif
    #ifndef  MPI_INT_FAST8_T
    # define MPI_INT_FAST8_T  MPI_INT8_T
    #endif
    #ifndef  MPI_INT_FAST16_T
    # define MPI_INT_FAST16_T  MPI_INT64_T
    #endif
    #ifndef  MPI_INT_FAST32_T
    # define MPI_INT_FAST32_T  MPI_INT64_T
    #endif
    #ifndef  MPI_INT_FAST64_T
    # define MPI_INT_FAST64_T  MPI_INT64_T
    #endif
    #ifndef  MPI_UINT_FAST8_T
    # define MPI_UINT_FAST8_T  MPI_UINT8_T
    #endif
    #ifndef  MPI_UINT_FAST16_T
    # define MPI_UINT_FAST16_T  MPI_UINT64_T
    #endif
    #ifndef  MPI_UINT_FAST32_T
    # define MPI_UINT_FAST32_T  MPI_UINT64_T
    #endif
    #ifndef  MPI_UINT_FAST64_T
    # define MPI_UINT_FAST64_T  MPI_UINT64_T
    #endif
    

    If you use a Makefile to organize your product, I would use something like

    CC      := mpicc
    CFLAGS  := -Wall -O2
    LDFLAGS := -lmpi
    
    all: your-main-program
    
    clean:
        @rm -f *.o extra_mpi_types_internal.h
    
    type-generator: type-generator.c
        $(CC) $(CFLAGS) $^ -o $@
    
    extra_mpi_types_internal.h: type-generator
        ./type-generator > $@
    
    %.o: %.c extra_mpi_types_internal.h
        $(CC) $(CFLAGS) $< -c -o $@
    
    your-main-program: all.o needed.o object.o files.o
        $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ 
    

    although this approach does mean that you cannot cross-compile MPI programs for a different architecture.


    Alternatively, you can use pre-defined compiler macros to determine the OS, hardware architecture, and C library used, to include a pre-prepared header file with the correct macro definitions:

    /* extra_mpi_types.h */
    #ifndef   EXTRA_MPI_TYPES_H
    
    #if defined(__linux__)
    #if   defined(__amd64__)
    #include <extra-linux-amd64.h>
    #elif defined(__i386__)
    #include <extra-linux-x86.h>
    #elif defined(__aarch64__)
    #include <extra-linux-arm64.h>
    #elif defined(__ARM_ARCH_4T__)
    #include <extra-linux-arm-4t.h>
    #else
    #error "Unsupported Linux hardware architecture"
    #endif
    
    #elif defined(_WIN64)
    #include <extra-win64.h>
    
    #elif defined(_WIN32)
    #include <extra-win32.h>
    
    #else
    #error  Unsupported operating system.
    #endif
    
    #endif /* EXTRA_MPI_TYPES_H */
    

    The contents for each of the above files (or rather, the architectures and operating systems as needed), can be either discovered using a C program like above, or by examining the C compiler and library header files.