== Initialization of libfabric structures == Every libfabric structure that is expected by libfabric functions to be allocated by either the application or provider (i.e., a structure that is an input parameter or output parameter to an application-facing libfabric function) should be initialized immediately upon allocation. This includes all structures that are intentionally transparent to the application or provider, and therefore includes things like event queue structures, for which space must be allocated by the application or provider. To do this, the libfabric library must also define 3 items for every "struct fi_foo" defined in the library: 1. A static initializer named FI_FOO_INITIALIZER. 2. Two functions with prototypes: static inline int fi_foo_init(*struct foo); static inline void fi_foo_destroy(&struct foo); This approach is analogous to that taken by POSIX threads structures. For example, for mutex variables POSIX defines: 1. The static initializer PTHREAD_MUTEX_INITIALIZER 2. Two functions: int pthread_mutex_init(pthread_mutex_t *__mutex, const pthread_mutexattr_t *__mutexattr); int pthread_mutex_destroy(pthread_mutex_t *__mutex); These items are meant to be used as follows to initialize every variable of type struct foo allocated by the application: struct fi_foo user_foo = FI_FOO_INITIALIZER; -- OR -- struct user_xyz { ... struct fi_foo user_foo; ... }; int user_xyz_init(struct user_xyz *ctx) { ... fi_foo_init(&ctx->user_foo); ... } void user_xyz_destroy(struct user_xyz *ctx) { ... fi_foo_destroy(&ctx->user_foo); ... } struct user_xyz abc; user_xyz_init(&abc); ... user_xyz-destroy(&abc); -- OR -- struct fi_foo *user_foo; user_foo = (struct fi_foo *)malloc(sizeof(*user_foo)); fi_foo_init(user_foo); ... fi_foo_destroy(user_foo); free(user_foo); We propose that these 3 items be added to libfabric for each structure defined by libfabric in order to facilitate (1) ease of programming; (2) correct initialization of the mask/version/size field based on the size/members of the structure at compile time; (3) binary compatibility between application and provider code compiled with different releases of the library. We propose that initializing a structure using some mechanism other than that listed below be considered implementation-defined behavior (by the provider/application), and that not initializing a structure being passed to a libfabric function (by pointer reference, i.e., struct fi_foo *) be considered undefined behavior. === Static initializer macros === In order to statically initialized a structure to some sane default values, a static initializer macro (i.e., FI_FOO_INITIALIZER for struct fi_foo) MUST be provided that should place the structure into a valid state (including version/mask fields). This macro must set the correct structure state (size/version) based on the compile-time version of libfabric, and must default initialize all fields (Rationale: this allows recompiling the application against a newer version of libfabric containing more fields than were available when the application was written). The application or provider will be expected to use this macro to initialize each API structure that it allocates on the stack or as a global/static variable. Example definition in libfabrics header file: #define FI_MR_ATTR_INITIALIZER { .mask = FI_MR_ATTR_MASK_V1, /* ... */ } Example use in application program: struct fi_mr_attr my_mr = FI_MR_ATTR_INITIALIZER; === Dynamic allocation functions === libfabric MUST provide a pair of static inline functions (fi_foo_init() and fi_foo_destroy() for each struct fi_foo) that the application or provider will be expected to use if the structure is dynamically allocated (this is most likely the case for all structures allocated by the provider). (Rationale: static inline functions will be guaranteed to be taken from libfabric at compile time and thus match the size of the structure at compile time.) The fi_foo_init() function must set the correct structure state size/version/mask based on the compile-time version of libfabric, and must default initialize all fields (rationale same as above). The application or provider will be expected to call the fi_foo_init() function after each instance of a dynamic allocation of a "struct foo". Destruction functions should not produce or report errors (Rationale: this helps with language bindings; i.e., C++ destructors should never throw exceptions). Possibly there could be another mechanism by which any error condition could be retrieved prior to destroying the object. Destruction of a NULL pointer should be a no-op. Destruction of an object still being used internally (e.g., a protection domain with still-active memory registrations) should be delayed, and the application will be responsible for not double-destroying these structures (although such a structure could easily be adapted to detect double destruction). Example definition in libfabrics header file: static inline int fi_mr_attr_init(struct fi_mr_attr *attr) { /* MUST initialize all fields to a sane default value * (possibly via memset to 0) */ /* MUST initialize mask to version of libfabric being compiled */ attr->mask = FI_MR_ATTR_MASK_V1; return 0; err: /* Can return error code if initialization failed */ return -fi_errno; } static inline void fi_mr_attr_destroy(struct fi_mr_attr *attr) { /* This can be a no-op for simple structures */ /* This function MUST NOT signal fatal errors to the application * that cannot be caught via another mechanism */ } == Consequences == Correct use of these items will guarantee (1) that structures allocated by an application will be correctly initialized for the structure defined in the header files with which the application was compiled; and (2) that structures allocated by a provider will be correctly initialized for the structure defined in the header files with which the provider was compiled. These can be different, and it will be up to the provider to reconcile the differences dynamically at run-time. In order to be compatible over all versions, the following simple rules need to be followed in defining the structures and the modifications to the structures: 1. Once a field is defined in a release, it cannot be undefined or reused in any future releases -- its use may be deprecated, but its allocation in the structure must never change or be reused. (To do so would mean a new provider could find it initialized incorrectly if it were allocated in an application compiled by a previous release.) This implies, for example, that a structure can never shrink in size from release to release, and that ALL fields, including the mask/version/size field, in each structure will have the same offset and size in ALL releases of the library. (i.e., that each library structure in an older release is a subset of that structure in each newer release.) 2. When a field is added to a structure in a new release, all references by a provider function in that new release to an instance of that structure that could have been allocated by an application and given to the provider function as a parameter must dynamically check the mask/version/size field of that structure in order to ensure the existence of the newly added field before accessing it.