Redefine the default boolean type to gmx_bool.
[alexxy/gromacs.git] / src / gmxlib / thread_mpi / tmpi_init.c
1 /*
2 This source code file is part of thread_mpi.  
3 Written by Sander Pronk, Erik Lindahl, and possibly others. 
4
5 Copyright (c) 2009, Sander Pronk, Erik Lindahl.
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions are met:
10 1) Redistributions of source code must retain the above copyright
11    notice, this list of conditions and the following disclaimer.
12 2) Redistributions in binary form must reproduce the above copyright
13    notice, this list of conditions and the following disclaimer in the
14    documentation and/or other materials provided with the distribution.
15 3) Neither the name of the copyright holders nor the
16    names of its contributors may be used to endorse or promote products
17    derived from this software without specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY US ''AS IS'' AND ANY
20 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL WE BE LIABLE FOR ANY
23 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 If you want to redistribute modifications, please consider that
31 scientific software is very special. Version control is crucial -
32 bugs must be traceable. We will be happy to consider code for
33 inclusion in the official distribution, but derived work should not
34 be called official thread_mpi. Details are found in the README & COPYING
35 files.
36 */
37
38
39 #ifdef HAVE_TMPI_CONFIG_H
40 #include "tmpi_config.h"
41 #endif
42
43 #ifdef HAVE_CONFIG_H
44 #include "config.h"
45 #endif
46
47 #ifdef HAVE_UNISTD_H
48 #include <unistd.h>
49 #endif
50
51 #include <errno.h>
52 #include <stdlib.h>
53 #include <stdio.h>
54 #include <string.h>
55 #if ! (defined( _WIN32 ) || defined( _WIN64 ) )
56 #include <sys/time.h>
57
58 #endif
59
60
61 #include "impl.h"
62
63 #ifdef TMPI_TRACE
64 #include <stdarg.h>
65 #endif
66
67
68
69
70
71
72 /* there are a few global variables that maintain information about the
73    running threads. Some are defined by the MPI standard: */
74 tMPI_Comm TMPI_COMM_WORLD=NULL;
75 tMPI_Group tMPI_GROUP_EMPTY=NULL;
76
77
78 /* the threads themselves (tmpi_comm only contains lists of pointers to this
79       structure */
80 struct tmpi_thread *threads=NULL;
81 int Nthreads=0;
82
83 /* thread info */
84 tMPI_Thread_key_t id_key; /* the key to get the thread id */
85
86
87
88 /* whether MPI has finalized (we need this to distinguish pre-inited from
89        post-finalized states */
90 static gmx_bool tmpi_finalized=FALSE;
91
92 /* misc. global information about MPI */
93 struct tmpi_global *tmpi_global=NULL;
94
95
96
97
98
99
100
101
102 /* start N threads with argc, argv (used by tMPI_Init)*/
103 void tMPI_Start_threads(gmx_bool main_returns, int N, int *argc, char ***argv, 
104                         void (*start_fn)(void*), void *start_arg,
105                         int (*start_fn_main)(int, char**));
106
107 /* starter function for threads; takes a void pointer to a
108       struct tmpi_starter_, which calls main() if tmpi_start_.fn == NULL */
109 static void* tMPI_Thread_starter(void *arg);
110
111 /* allocate and initialize the data associated with a thread structure */
112 static void tMPI_Thread_init(struct tmpi_thread *th);
113 /* deallocate the data associated with a thread structure */
114 static void tMPI_Thread_destroy(struct tmpi_thread *th);
115
116
117
118
119 #ifdef TMPI_TRACE
120 void tMPI_Trace_print(const char *fmt, ...)
121 {
122     va_list argp;
123     struct tmpi_thread* th=tMPI_Get_current();
124     static tMPI_Thread_mutex_t mtx=TMPI_THREAD_MUTEX_INITIALIZER;
125
126     tMPI_Thread_mutex_lock(&mtx);
127     if (threads)
128         printf("THREAD %02d: ", (int)(th-threads));
129     else
130         printf("THREAD main: ");
131     va_start(argp, fmt);
132     vprintf(fmt, argp);
133     printf("\n");
134     fflush(stdout);
135     va_end(argp);
136     tMPI_Thread_mutex_unlock(&mtx);
137 }
138 #endif
139
140
141 void *tMPI_Malloc(size_t size)
142 {
143     void *ret=(void*)malloc(size);
144
145     if (!ret)
146     {
147         tMPI_Error(TMPI_COMM_WORLD, TMPI_ERR_MALLOC);
148     }
149     return ret;
150 }
151
152 void *tMPI_Realloc(void *p, size_t size)
153 {
154     void *ret=(void*)realloc(p, size);
155     if (!ret)
156     {
157         tMPI_Error(TMPI_COMM_WORLD, TMPI_ERR_MALLOC);
158     }
159     return ret;
160 }
161
162
163 #if 0
164 struct tmpi_thread *tMPI_Get_current(void)
165 {
166     if (!threads)
167         return NULL;
168
169     return (struct tmpi_thread*)tMPI_thread_getspecific(id_key);
170
171
172
173 unsigned int tMPI_Threadnr(struct tmpi_thread *thr)
174 {
175     return thr-threads;
176 }
177 #endif
178 #if 0
179 unsigned int tMPI_This_threadnr(void)
180 {
181     return tMPI_Get_current()-threads;
182 }
183
184 struct tmpi_thread *tMPI_Get_thread(tMPI_Comm comm, int rank)
185 {
186     /* check destination */
187     if ( (rank < 0) || (rank > comm->grp.N) )
188     {
189         tMPI_Error(comm, TMPI_ERR_GROUP_RANK);
190         return NULL;
191     }
192     return comm->grp.peers[rank];
193 }
194 #endif
195
196 gmx_bool tMPI_Is_master(void)
197 {
198     /* if there are no other threads, we're the main thread */
199     if ( (!TMPI_COMM_WORLD) || TMPI_COMM_WORLD->grp.N==0)
200         return TRUE;
201
202     /* otherwise we know this through thread specific data: */
203     /* whether the thread pointer points to the head of the threads array */
204     return (gmx_bool)(tMPI_Get_current() == threads); 
205 }
206
207 tMPI_Comm tMPI_Get_comm_self(void)
208 {
209     struct tmpi_thread* th=tMPI_Get_current();
210     return th->self_comm;
211 }
212
213
214 int tMPI_Get_N(int *argc, char ***argv, const char *optname, int *nthreads)
215 {
216     int i;
217     int ret=TMPI_SUCCESS;
218
219     *nthreads=0;
220     if (!optname)
221     {
222         i=0;
223     }
224     else
225     {
226         for(i=1;i<*argc;i++)
227         {
228             if (strcmp(optname, (*argv)[i]) == 0)
229             {
230                 break;
231             }
232         }
233     }
234     if (i+1 < (*argc))
235     {
236         /* the number of processes is an argument */
237         char *end;
238         *nthreads=strtol((*argv)[i+1], &end, 10);
239         if ( !end || (*end != 0) )
240         {
241             *nthreads=0;
242             ret=TMPI_FAILURE;
243         }
244     }
245     if (*nthreads<1)
246     {
247         *nthreads=tMPI_Get_recommended_nthreads();
248     }
249
250     return ret;
251 }
252
253 static void tMPI_Thread_init(struct tmpi_thread *th)
254 {
255     int N_envelopes=(Nthreads+1)*N_EV_ALLOC;  
256     int N_send_envelopes=N_EV_ALLOC;  
257     int N_reqs=(Nthreads+1)*N_EV_ALLOC;  
258     int i;
259
260     /* we set our thread id, as a thread-specific piece of global data. */
261     tMPI_Thread_setspecific(id_key, th);
262
263     /* allocate comm.self */
264     th->self_comm=tMPI_Comm_alloc(TMPI_COMM_WORLD, 1);
265     th->self_comm->grp.peers[0]=th;
266
267     /* allocate envelopes */
268     tMPI_Free_env_list_init( &(th->envelopes), N_envelopes );
269     /* recv list */
270     tMPI_Recv_env_list_init( &(th->evr));
271     /* send lists */
272     th->evs=(struct send_envelope_list*)tMPI_Malloc(
273                         sizeof(struct send_envelope_list)*Nthreads);
274     for(i=0;i<Nthreads;i++)
275     {
276         tMPI_Send_env_list_init( &(th->evs[i]), N_send_envelopes);
277     }
278
279     tMPI_Atomic_set( &(th->ev_outgoing_received), 0);
280
281     tMPI_Event_init( &(th->p2p_event) );
282
283     /* allocate requests */
284     tMPI_Req_list_init(&(th->rql), N_reqs);
285
286 #ifdef USE_COLLECTIVE_COPY_BUFFER
287     /* allcate copy_buffer list */
288     tMPI_Copy_buffer_list_init(&(th->cbl_multi), (Nthreads+1)*(N_COLL_ENV+1),
289                                Nthreads*COPY_BUFFER_SIZE);
290 #endif
291
292 #ifdef TMPI_PROFILE
293     tMPI_Profile_init(&(th->profile));
294 #endif
295     /* now wait for all other threads to come on line, before we
296        start the MPI program */
297     tMPI_Thread_barrier_wait( &(tmpi_global->barrier) );
298 }
299
300
301 static void tMPI_Thread_destroy(struct tmpi_thread *th)
302 {
303     int i;
304
305     tMPI_Recv_env_list_destroy( &(th->evr));
306     for(i=0;i<Nthreads;i++)
307     {
308         tMPI_Send_env_list_destroy( &(th->evs[i]));
309     }
310     free(th->evs);
311     tMPI_Free_env_list_destroy( &(th->envelopes) );
312     tMPI_Event_destroy( &(th->p2p_event) );
313     tMPI_Req_list_destroy( &(th->rql) );
314
315 #ifdef USE_COLLECTIVE_COPY_BUFFER
316     tMPI_Copy_buffer_list_destroy(&(th->cbl_multi));
317 #endif
318
319     for(i=0;i<th->argc;i++)
320     {
321         free(th->argv[i]);
322     }
323 }
324
325 static void tMPI_Global_init(struct tmpi_global *g, int Nthreads)
326 {
327     g->usertypes=NULL;
328     g->N_usertypes=0;
329     g->Nalloc_usertypes=0;
330     tMPI_Thread_mutex_init(&(g->timer_mutex));
331     tMPI_Spinlock_init(&(g->datatype_lock));
332
333     tMPI_Thread_barrier_init( &(g->barrier), Nthreads);
334
335 #if ! (defined( _WIN32 ) || defined( _WIN64 ) )
336     /* the time at initialization. */
337     gettimeofday( &(g->timer_init), NULL);
338 #else
339     /* the time at initialization. */
340     g->timer_init=GetTickCount();
341 #endif
342
343 }
344
345 static void tMPI_Global_destroy(struct tmpi_global *g)
346 {
347     tMPI_Thread_mutex_destroy(&(g->timer_mutex));
348 }
349
350
351
352
353 static void* tMPI_Thread_starter(void *arg)
354 {
355     struct tmpi_thread *th=(struct tmpi_thread*)arg;
356
357 #ifdef TMPI_TRACE
358     tMPI_Trace_print("Created thread nr. %d", (int)(th-threads));
359 #endif
360
361     tMPI_Thread_init(th);
362
363     /* start_fn, start_arg, argc and argv were set by the calling function */ 
364     if (! th->start_fn )
365     {
366         th->start_fn_main(th->argc, th->argv);
367     }
368     else
369     {
370         th->start_fn(th->start_arg);
371         if (!tmpi_finalized)
372             tMPI_Finalize();
373     }
374
375     return 0;
376 }
377
378
379 void tMPI_Start_threads(gmx_bool main_returns, int N, int *argc, char ***argv, 
380                         void (*start_fn)(void*), void *start_arg,
381                         int (*start_fn_main)(int, char**))
382 {
383 #ifdef TMPI_TRACE
384     tMPI_Trace_print("tMPI_Start_threads(%d, %p, %p, %p, %p)", N, argc,
385                        argv, start_fn, start_arg);
386 #endif
387     if (N>0) 
388     {
389         int i;
390
391         tmpi_finalized=FALSE;
392         Nthreads=N;
393
394         /* allocate global data */
395         tmpi_global=(struct tmpi_global*)
396                         tMPI_Malloc(sizeof(struct tmpi_global));
397         tMPI_Global_init(tmpi_global, N);
398
399         /* allocate world and thread data */
400         threads=(struct tmpi_thread*)tMPI_Malloc(sizeof(struct tmpi_thread)*N);
401         TMPI_COMM_WORLD=tMPI_Comm_alloc(NULL, N);
402         tMPI_GROUP_EMPTY=tMPI_Group_alloc();
403
404         if (tMPI_Thread_key_create(&id_key, NULL))
405         {
406             tMPI_Error(TMPI_COMM_WORLD, TMPI_ERR_INIT);
407         }
408         for(i=0;i<N;i++)
409         {
410             TMPI_COMM_WORLD->grp.peers[i]=&(threads[i]);
411
412             /* copy argc, argv */
413             if (argc && argv)
414             {
415                 int j;
416                 threads[i].argc=*argc;
417                 threads[i].argv=(char**)tMPI_Malloc(threads[i].argc*
418                                                    sizeof(char*));
419                 for(j=0;j<threads[i].argc;j++)
420                 {
421 #if ! (defined( _WIN32 ) || defined( _WIN64 ) )
422                     threads[i].argv[j]=strdup( (*argv)[j] );
423 #else
424                     threads[i].argv[j]=_strdup( (*argv)[j] );
425 #endif
426                 }
427             }
428             else
429             {
430                 threads[i].argc=0;
431                 threads[i].argv=NULL;
432             }
433             threads[i].start_fn=start_fn;
434             threads[i].start_fn_main=start_fn_main;
435             threads[i].start_arg=start_arg;
436         }
437         for(i=1;i<N;i++) /* zero is the main thread */
438         {
439             if (tMPI_Thread_create(&(threads[i].thread_id), 
440                                   tMPI_Thread_starter,
441                                   (void*)&(threads[i]) ) )
442             {
443                 tMPI_Error(TMPI_COMM_WORLD, TMPI_ERR_INIT);
444             }
445         }
446         /* the main thread now also runs start_fn if we don't want
447            it to return */
448         if (!main_returns)
449             tMPI_Thread_starter((void*)&(threads[0]));
450         else
451             tMPI_Thread_init(&(threads[0]));
452     }
453 }
454
455
456 int tMPI_Init(int *argc, char ***argv, int (*start_function)(int, char**))
457 {
458 #ifdef TMPI_TRACE
459     tMPI_Trace_print("tMPI_Init(%p, %p, %p)", argc, argv, start_function);
460 #endif
461
462
463     if (TMPI_COMM_WORLD==0) /* we're the main process */
464     {
465         int N=0;
466         tMPI_Get_N(argc, argv, "-nt", &N);
467         tMPI_Start_threads(FALSE, N, argc, argv, NULL, NULL, start_function);
468     }
469     else
470     {
471         /* if we're a sub-thread we need don't need to do anyhing, because 
472            everything has already been set up by either the main thread, 
473            or the thread runner function.*/
474     }
475     return TMPI_SUCCESS;
476 }
477
478 int tMPI_Init_fn(int main_thread_returns, int N, 
479                  void (*start_function)(void*), void *arg)
480 {
481 #ifdef TMPI_TRACE
482     tMPI_Trace_print("tMPI_Init_fn(%d, %p, %p)", N, start_function, arg);
483 #endif
484
485     if (N<1)
486     {
487         N=tMPI_Get_recommended_nthreads();
488     }
489
490     if (TMPI_COMM_WORLD==0 && N>=1) /* we're the main process */
491     {
492         tMPI_Start_threads(main_thread_returns, N, 0, 0, start_function, arg, 
493                            NULL);
494     }
495     return TMPI_SUCCESS;
496 }
497
498 int tMPI_Initialized(int *flag)
499 {
500 #ifdef TMPI_TRACE
501     tMPI_Trace_print("tMPI_Initialized(%p)", flag);
502 #endif
503
504     *flag=(TMPI_COMM_WORLD && !tmpi_finalized);
505
506     return TMPI_SUCCESS;
507 }
508
509 int tMPI_Finalize(void)
510 {
511     int i;
512 #ifdef TMPI_TRACE
513     tMPI_Trace_print("tMPI_Finalize()");
514 #endif
515 #ifdef TMPI_DEBUG
516     printf("%5d: tMPI_Finalize called\n", tMPI_This_threadnr());
517     fflush(stdout);
518 #endif
519
520 #ifdef TMPI_PROFILE
521     {
522         struct tmpi_thread *cur=tMPI_Get_current();
523
524         tMPI_Profile_stop( &(cur->profile) );
525         tMPI_Thread_barrier_wait( &(tmpi_global->barrier) );
526
527         if (tMPI_Is_master())
528         {
529             tMPI_Profiles_summarize(Nthreads, threads);
530         }
531     }
532 #endif
533     tMPI_Thread_barrier_wait( &(tmpi_global->barrier) );
534
535     if (tMPI_Is_master())
536     {
537
538         /* we just wait for all threads to finish; the order isn't very 
539            relevant, as all threads should arrive at their endpoints soon. */
540         for(i=1;i<Nthreads;i++)
541         {
542             if (tMPI_Thread_join(threads[i].thread_id, NULL))
543             {
544                 tMPI_Error(TMPI_COMM_WORLD, TMPI_ERR_FINALIZE);
545             }
546             tMPI_Thread_destroy(&(threads[i]));
547         }
548         /* at this point, we are the only thread left, so we can 
549            destroy the global structures with impunity. */
550         tMPI_Thread_destroy(&(threads[0]));
551         free(threads);
552
553         tMPI_Thread_key_delete(id_key);
554         /* de-allocate all the comm stuctures. */
555         {
556             tMPI_Comm cur=TMPI_COMM_WORLD->next;
557             while(cur && (cur!=TMPI_COMM_WORLD) )
558             {
559                 tMPI_Comm next=cur->next;
560                 tMPI_Comm_destroy(cur);
561                 cur=next;
562             }
563             tMPI_Comm_destroy(TMPI_COMM_WORLD);
564         }
565
566         tMPI_Group_free(&tMPI_GROUP_EMPTY);
567         threads=0;
568         TMPI_COMM_WORLD=NULL;
569         tMPI_GROUP_EMPTY=NULL;
570         Nthreads=0;
571
572         /* deallocate the 'global' structure */
573         tMPI_Global_destroy(tmpi_global);
574         free(tmpi_global);
575
576         tmpi_finalized=TRUE;
577     }
578     else
579     {
580         tMPI_Thread_exit(0);
581     }
582     return TMPI_SUCCESS;
583 }
584
585
586 int tMPI_Finalized(int *flag)
587 {
588 #ifdef TMPI_TRACE
589     tMPI_Trace_print("tMPI_Finalized(%p)", flag);
590 #endif
591     *flag=tmpi_finalized;
592
593     return TMPI_SUCCESS;
594 }
595
596
597
598 int tMPI_Abort(tMPI_Comm comm, int errorcode)
599 {
600 #ifdef TMPI_TRACE
601     tMPI_Trace_print("tMPI_Abort(%p, %d)", comm, errorcode);
602 #endif
603 #if 0
604     /* we abort(). This way we can run a debugger on it */
605     fprintf(stderr, "tMPI_Abort called with error code %d",errorcode);
606     if (comm==TMPI_COMM_WORLD)
607         fprintf(stderr, " on TMPI_COMM_WORLD");
608     fprintf(stderr,"\n");
609     fflush(stdout);
610
611     abort();
612 #else
613     /* we just kill all threads, but not the main process */
614     
615     if (tMPI_Is_master())
616     {
617         if (comm==TMPI_COMM_WORLD)
618             fprintf(stderr, 
619                "tMPI_Abort called on TMPI_COMM_WORLD main with errorcode=%d\n",
620                errorcode);
621         else
622         fprintf(stderr, "tMPI_Abort called on main thread with errorcode=%d\n",
623                 errorcode);
624         fflush(stderr);
625         exit(errorcode);
626     }
627     else
628     {
629         int *ret;
630         /* kill myself */
631         fprintf(stderr, "tMPI_Abort called with error code %d on thread %d\n", 
632                         errorcode, tMPI_This_threadnr());
633         fflush(stderr);
634         ret=(int*)malloc(sizeof(int));
635         tMPI_Thread_exit(ret);
636     }
637 #endif
638     return TMPI_SUCCESS;
639 }
640
641
642 int tMPI_Get_processor_name(char *name, int *resultlen)
643 {
644     int nr=tMPI_Threadnr(tMPI_Get_current());
645     unsigned int digits=0;
646     const unsigned int base=10;
647
648 #ifdef TMPI_TRACE
649     tMPI_Trace_print("tMPI_Get_processor_name(%p, %p)", name, resultlen);
650 #endif
651     /* we don't want to call sprintf here (it turns out to be not entirely
652        thread-safe on Mac OS X, for example), so we do it our own way: */
653
654     /* first determine number of digits */
655     {
656         int rest=nr;
657         while(rest > 0)
658         {
659             rest /= base;
660             digits++;
661         }
662         if (digits==0)
663             digits=1;
664     }
665 #if ! (defined( _WIN32 ) || defined( _WIN64 ) )
666     strcpy(name, "thread #");
667 #else
668     strncpy_s(name, TMPI_MAX_PROCESSOR_NAME, "thread #", TMPI_MAX_PROCESSOR_NAME);
669 #endif
670     /* now construct the number */
671     {
672         size_t len=strlen(name);
673         unsigned int i;
674         int rest=nr;
675
676         for(i=0;i<digits;i++)
677         {
678             size_t pos=len + (digits-i-1);
679             if (pos < (TMPI_MAX_PROCESSOR_NAME -1) )
680                 name[ pos ]=(char)('0' + rest%base);
681             rest /= base;
682         }
683         if ( (digits+len) < TMPI_MAX_PROCESSOR_NAME)
684             name[digits + len]='\0';
685         else
686             name[TMPI_MAX_PROCESSOR_NAME]='\0';
687
688     }
689     if (resultlen)
690         *resultlen=(int)strlen(name); /* For some reason the MPI standard
691                                          uses ints instead of size_ts for
692                                          sizes. */
693     return TMPI_SUCCESS;
694 }
695
696
697
698
699
700 /* TODO: there must be better ways to do this */
701 double tMPI_Wtime(void)
702 {
703     double ret=0;
704
705 #ifdef TMPI_TRACE
706     tMPI_Trace_print("tMPI_Wtime()");
707 #endif
708
709 #if ! (defined( _WIN32 ) || defined( _WIN64 ) )
710     {
711         struct timeval tv;
712         long int secdiff;
713         int usecdiff;
714
715         gettimeofday(&tv, NULL);
716         secdiff = tv.tv_sec - tmpi_global->timer_init.tv_sec;
717         usecdiff = tv.tv_usec - tmpi_global->timer_init.tv_usec;
718
719         ret=(double)secdiff + 1e-6*usecdiff;
720     }
721 #else
722     {
723         DWORD tv=GetTickCount();
724    
725         /* the windows absolute time GetTickCount() wraps around in ~49 days,
726            so it's safer to always use differences, and assume that our
727            program doesn't run that long.. */
728         ret=1e-3*((unsigned int)(tv - tmpi_global->timer_init));
729     }
730 #endif
731     return ret;
732 }
733
734 double tMPI_Wtick(void)
735 {
736 #if ! (defined( _WIN32 ) || defined( _WIN64 ) )
737     /* In Unix, we don't really know. Any modern OS should be at least
738        this precise, though */
739     return 1./100.;
740 #else
741     /* According to the Windows documentation, this is about right: */
742     return 1./100.;
743 #endif
744 }
745
746
747
748
749
750
751
752 int tMPI_Get_count(tMPI_Status *status, tMPI_Datatype datatype, int *count)
753 {
754 #ifdef TMPI_TRACE
755     tMPI_Trace_print("tMPI_Get_count(%p, %p, %p)", status, datatype, count);
756 #endif
757     if (!status)
758     {
759         return tMPI_Error(TMPI_COMM_WORLD, TMPI_ERR_STATUS);
760     }
761     *count = (int)(status->transferred/datatype->size);
762     return TMPI_SUCCESS;
763 }
764
765
766