root/mpich2/trunk/src/mpid/common/datatype/dataloop/dataloop_create_struct.c @ 3939

Revision 3939, 18.2 KB (checked in by goodell, 9 months ago)

Followup to r3927 (hvector assert bug).

This adds augments the hvector-zeros test to include all zero count arguments
and then fixes the resulting bug in the dataloop code. This is a tweaked
version of Rob Ross' patch.

No reviewer.

Line 
1/* -*- Mode: C; c-basic-offset:4 ; -*- */
2
3/*
4 *  (C) 2001 by Argonne National Laboratory.
5 *      See COPYRIGHT in top-level directory.
6 */
7
8#include "./dataloop.h"
9
10#ifndef PREPEND_PREFIX
11#error "You must explicitly include a header that sets the PREPEND_PREFIX and includes dataloop_parts.h"
12#endif
13
14static int DLOOP_Dataloop_create_struct_memory_error(void);
15static int DLOOP_Dataloop_create_unique_type_struct(int count,
16                                                    int *blklens,
17                                                    MPI_Aint *disps,
18                                                    DLOOP_Type *oldtypes,
19                                                    int type_pos,
20                                                    DLOOP_Dataloop **dlp_p,
21                                                    int *dlsz_p,
22                                                    int *dldepth_p,
23                                                    int flag);
24static int DLOOP_Dataloop_create_basic_all_bytes_struct(
25               int count,
26               int *blklens,
27               MPI_Aint *disps,
28               DLOOP_Type *oldtypes,
29               DLOOP_Dataloop **dlp_p,
30               int *dlsz_p,
31               int *dldepth_p,
32               int flag);
33static int DLOOP_Dataloop_create_flattened_struct(int count,
34                                                  int *blklens,
35                                                  MPI_Aint *disps,
36                                                  DLOOP_Type *oldtypes,
37                                                  DLOOP_Dataloop **dlp_p,
38                                                  int *dlsz_p,
39                                                  int *dldepth_p,
40                                                  int flag);
41
42/*@
43  Dataloop_create_struct - create the dataloop representation for a
44  struct datatype
45
46  Input Parameters:
47+ count - number of blocks in vector
48. blklens - number of elements in each block
49. disps - offsets of blocks from start of type in bytes
50- oldtypes - types (using handle) of datatypes on which vector is based
51
52  Output Parameters:
53+ dlp_p - pointer to address in which to place pointer to new dataloop
54- dlsz_p - pointer to address in which to place size of new dataloop
55
56  Return Value:
57  0 on success, -1 on failure.
58
59  Notes:
60  This function relies on others, like Dataloop_create_indexed, to create
61  types in some cases. This call (like all the rest) takes int blklens
62  and MPI_Aint displacements, so it's possible to overflow when working
63  with a particularly large struct type in some cases. This isn't detected
64  or corrected in this code at this time.
65
66@*/
67int PREPEND_PREFIX(Dataloop_create_struct)(int count,
68                                           int *blklens,
69                                           MPI_Aint *disps,
70                                           DLOOP_Type *oldtypes,
71                                           DLOOP_Dataloop **dlp_p,
72                                           int *dlsz_p,
73                                           int *dldepth_p,
74                                           int flag)
75{
76    int err, i, nr_basics = 0, nr_derived = 0, type_pos = 0;
77
78    DLOOP_Type first_basic = MPI_DATATYPE_NULL,
79        first_derived = MPI_DATATYPE_NULL;
80
81    /* variables used in general case only */
82    int loop_idx, new_loop_sz, new_loop_depth;
83    int old_loop_sz = 0, old_loop_depth = 0;
84
85    DLOOP_Dataloop *new_dlp, *curpos;
86
87    /* if count is zero, handle with contig code, call it a int */
88    if (count == 0)
89    {
90        err = PREPEND_PREFIX(Dataloop_create_contiguous)(0,
91                                                         MPI_INT,
92                                                         dlp_p,
93                                                         dlsz_p,
94                                                         dldepth_p,
95                                                         flag);
96        return err;
97    }
98
99    /* browse the old types and characterize */
100    for (i=0; i < count; i++)
101    {
102        /* ignore type elements with a zero blklen */
103        if (blklens[i] == 0) continue;
104
105        if (oldtypes[i] != MPI_LB && oldtypes[i] != MPI_UB)
106        {
107            int is_builtin;
108
109            is_builtin =
110                (DLOOP_Handle_hasloop_macro(oldtypes[i])) ? 0 : 1;
111
112            if (is_builtin)
113            {
114                if (nr_basics == 0)
115                {
116                    first_basic = oldtypes[i];
117                    type_pos = i;
118                }
119                else if (oldtypes[i] != first_basic)
120                {
121                    first_basic = MPI_DATATYPE_NULL;
122                }
123                nr_basics++;
124            }
125            else /* derived type */
126            {
127                if (nr_derived == 0)
128                {
129                    first_derived = oldtypes[i];
130                    type_pos = i;
131                }
132                else if (oldtypes[i] != first_derived)
133                {
134                    first_derived = MPI_DATATYPE_NULL;
135                }
136                nr_derived++;
137            }
138        }
139    }
140
141    /* note on optimizations:
142     *
143     * because LB, UB, and extent calculations are handled as part of
144     * the Datatype, we can safely ignore them in all our calculations
145     * here.
146     */
147
148    /* optimization:
149     *
150     * if there were only MPI_LBs and MPI_UBs in the struct type,
151     * treat it as a zero-element contiguous (just as count == 0).
152     */
153    if (nr_basics == 0 && nr_derived == 0)
154    {
155        err = PREPEND_PREFIX(Dataloop_create_contiguous)(0,
156                                                         MPI_INT,
157                                                         dlp_p,
158                                                         dlsz_p,
159                                                         dldepth_p,
160                                                         flag);
161        return err;
162    }
163
164    /* optimization:
165     *
166     * if there is only one unique instance of a type in the struct, treat it
167     * as a blockindexed type.
168     *
169     * notes:
170     *
171     * if the displacement happens to be zero, the blockindexed code will
172     * optimize this into a contig.
173     */
174    if (nr_basics + nr_derived == 1)
175    {
176        /* type_pos is index to only real type in array */
177        err = PREPEND_PREFIX(Dataloop_create_blockindexed)
178            (1, /* count */
179             blklens[type_pos],
180             &disps[type_pos],
181             1, /* displacement in bytes */
182             oldtypes[type_pos],
183             dlp_p,
184             dlsz_p,
185             dldepth_p,
186             flag);
187
188        return err;
189    }
190
191    /* optimization:
192     *
193     * if there only one unique type (more than one instance) in the
194     * struct, treat it as an indexed type.
195     *
196     * notes:
197     *
198     * this will apply to a single type with an LB/UB, as those
199     * are handled elsewhere.
200     *
201     */
202    if (((nr_derived == 0) && (first_basic != MPI_DATATYPE_NULL)) ||
203        ((nr_basics == 0) && (first_derived != MPI_DATATYPE_NULL)))
204    {
205        return DLOOP_Dataloop_create_unique_type_struct(count,
206                                                        blklens,
207                                                        disps,
208                                                        oldtypes,
209                                                        type_pos,
210                                                        dlp_p,
211                                                        dlsz_p,
212                                                        dldepth_p,
213                                                        flag);
214    }
215
216    /* optimization:
217     *
218     * if there are no derived types and caller indicated either a
219     * homogeneous system or the "all bytes" conversion, convert
220     * everything to bytes and use an indexed type.
221     */
222    if (nr_derived == 0 && ((flag == DLOOP_DATALOOP_HOMOGENEOUS) ||
223                            (flag == DLOOP_DATALOOP_ALL_BYTES)))
224    {
225        return DLOOP_Dataloop_create_basic_all_bytes_struct(count,
226                                                            blklens,
227                                                            disps,
228                                                            oldtypes,
229                                                            dlp_p,
230                                                            dlsz_p,
231                                                            dldepth_p,
232                                                            flag);
233    }
234
235    /* optimization:
236     *
237     * if caller asked for homogeneous or all bytes representation,
238     * flatten the type and store it as an indexed type so that
239     * there are no branches in the dataloop tree.
240     */
241    if ((flag == DLOOP_DATALOOP_HOMOGENEOUS) ||
242             (flag == DLOOP_DATALOOP_ALL_BYTES))
243    {
244        return DLOOP_Dataloop_create_flattened_struct(count,
245                                                      blklens,
246                                                      disps,
247                                                      oldtypes,
248                                                      dlp_p,
249                                                      dlsz_p,
250                                                      dldepth_p,
251                                                      flag);
252    }
253
254    /* scan through types and gather derived type info */
255    for (i=0; i < count; i++)
256    {
257        /* ignore type elements with a zero blklen */
258        if (blklens[i] == 0) continue;
259
260        if (DLOOP_Handle_hasloop_macro(oldtypes[i]))
261        {
262            int tmp_loop_depth, tmp_loop_sz;
263
264            DLOOP_Handle_get_loopdepth_macro(oldtypes[i], tmp_loop_depth, flag);
265            DLOOP_Handle_get_loopsize_macro(oldtypes[i], tmp_loop_sz, flag);
266
267            if (tmp_loop_depth > old_loop_depth)
268            {
269                old_loop_depth = tmp_loop_depth;
270            }
271            old_loop_sz += tmp_loop_sz;
272        }
273    }
274
275    /* general case below: 2 or more distinct types that are either
276     * basics or derived, and for which we want to preserve the types
277     * themselves.
278     */
279
280    if (nr_basics > 0)
281    {
282        /* basics introduce an extra level of depth, so if our new depth
283         * must be at least 2 if there are basics.
284         */
285        new_loop_depth = ((old_loop_depth+1) > 2) ? (old_loop_depth+1) : 2;
286    }
287    else
288    {
289        new_loop_depth = old_loop_depth + 1;
290    }
291
292    PREPEND_PREFIX(Dataloop_struct_alloc)((DLOOP_Count) nr_basics + nr_derived,
293                                          old_loop_sz,
294                                          nr_basics,
295                                          &curpos,
296                                          &new_dlp,
297                                          &new_loop_sz);
298    /* --BEGIN ERROR HANDLING-- */
299    if (!new_dlp)
300    {
301        return DLOOP_Dataloop_create_struct_memory_error();
302    }
303    /* --END ERROR HANDLING-- */
304
305
306    new_dlp->kind = DLOOP_KIND_STRUCT;
307    new_dlp->el_size = -1; /* not valid for struct */
308    new_dlp->el_extent = -1; /* not valid for struct; see el_extent_array */
309    new_dlp->el_type = MPI_DATATYPE_NULL; /* not valid for struct */
310
311    new_dlp->loop_params.s_t.count = (DLOOP_Count) nr_basics + nr_derived;
312
313    /* note: curpos points to first byte in "old dataloop" region of
314     * newly allocated space.
315     */
316
317    for (i=0, loop_idx = 0; i < count; i++)
318    {
319        int is_builtin;
320
321        /* ignore type elements with a zero blklen */
322        if (blklens[i] == 0) continue;
323
324        is_builtin = (DLOOP_Handle_hasloop_macro(oldtypes[i])) ? 0 : 1;
325
326        if (is_builtin)
327        {
328            DLOOP_Dataloop *dummy_dlp;
329            int dummy_sz, dummy_depth;
330
331            /* LBs and UBs already taken care of -- skip them */
332            if (oldtypes[i] == MPI_LB || oldtypes[i] == MPI_UB)
333            {
334                continue;
335            }
336
337            /* build a contig dataloop for this basic and point to that
338             *
339             * optimization:
340             *
341             * push the count (blklen) from the struct down into the
342             * contig so we can process more at the leaf.
343             */
344            err = PREPEND_PREFIX(Dataloop_create_contiguous)(blklens[i],
345                                                             oldtypes[i],
346                                                             &dummy_dlp,
347                                                             &dummy_sz,
348                                                             &dummy_depth,
349                                                             flag);
350
351            /* --BEGIN ERROR HANDLING-- */
352            if (err) {
353                /* TODO: FREE ALLOCATED RESOURCES */
354                return -1;
355            }
356            /* --END ERROR HANDLING-- */
357
358            /* copy the new contig loop into place in the struct memory
359             * region
360             */
361            PREPEND_PREFIX(Dataloop_copy)(curpos, dummy_dlp, dummy_sz);
362            new_dlp->loop_params.s_t.dataloop_array[loop_idx] = curpos;
363            curpos = (DLOOP_Dataloop *) ((char *) curpos + dummy_sz);
364
365            /* we stored the block size in the contig -- use 1 here */
366            new_dlp->loop_params.s_t.blocksize_array[loop_idx] = 1;
367            new_dlp->loop_params.s_t.el_extent_array[loop_idx] =
368                ((DLOOP_Offset) blklens[i]) * dummy_dlp->el_extent;
369            PREPEND_PREFIX(Dataloop_free)(&dummy_dlp);
370        }
371        else
372        {
373            DLOOP_Dataloop *old_loop_ptr;
374            int old_loop_sz;
375            DLOOP_Offset old_extent;
376
377            DLOOP_Handle_get_loopptr_macro(oldtypes[i], old_loop_ptr, flag);
378            DLOOP_Handle_get_loopsize_macro(oldtypes[i], old_loop_sz, flag);
379            DLOOP_Handle_get_extent_macro(oldtypes[i], old_extent);
380
381            PREPEND_PREFIX(Dataloop_copy)(curpos, old_loop_ptr, old_loop_sz);
382            new_dlp->loop_params.s_t.dataloop_array[loop_idx] = curpos;
383            curpos = (DLOOP_Dataloop *) ((char *) curpos + old_loop_sz);
384
385            new_dlp->loop_params.s_t.blocksize_array[loop_idx] =
386                (DLOOP_Count) blklens[i];
387            new_dlp->loop_params.s_t.el_extent_array[loop_idx] =
388                old_extent;
389        }
390        new_dlp->loop_params.s_t.offset_array[loop_idx] =
391            (DLOOP_Offset) disps[i];
392        loop_idx++;
393    }
394
395    *dlp_p     = new_dlp;
396    *dlsz_p    = new_loop_sz;
397    *dldepth_p = new_loop_depth;
398
399    return 0;
400}
401
402/* --BEGIN ERROR HANDLING-- */
403static int DLOOP_Dataloop_create_struct_memory_error(void)
404{
405    return -1;
406}
407/* --END ERROR HANDLING-- */
408
409static int DLOOP_Dataloop_create_unique_type_struct(int count,
410                                                    int *blklens,
411                                                    MPI_Aint *disps,
412                                                    DLOOP_Type *oldtypes,
413                                                    int type_pos,
414                                                    DLOOP_Dataloop **dlp_p,
415                                                    int *dlsz_p,
416                                                    int *dldepth_p,
417                                                    int flag)
418{
419    /* the same type used more than once in the array; type_pos
420     * indexes to the first of these.
421     */
422    int i, err, *tmp_blklens, cur_pos = 0;
423    DLOOP_Offset *tmp_disps;
424
425    /* count is an upper bound on number of type instances */
426    tmp_blklens = (int *) DLOOP_Malloc(count * sizeof(int));
427    /* --BEGIN ERROR HANDLING-- */
428    if (!tmp_blklens) {
429        /* TODO: ??? */
430        return DLOOP_Dataloop_create_struct_memory_error();
431    }
432    /* --END ERROR HANDLING-- */
433
434    tmp_disps = (DLOOP_Offset *)
435        DLOOP_Malloc(count * sizeof(DLOOP_Offset));
436    /* --BEGIN ERROR HANDLING-- */
437    if (!tmp_disps) {
438        DLOOP_Free(tmp_blklens);
439        /* TODO: ??? */
440        return DLOOP_Dataloop_create_struct_memory_error();
441    }
442    /* --END ERROR HANDLING-- */
443
444    for (i=type_pos; i < count; i++)
445    {
446        if (oldtypes[i] == oldtypes[type_pos] && blklens != 0)
447        {
448            tmp_blklens[cur_pos] = blklens[i];
449            tmp_disps[cur_pos]   = disps[i];
450            cur_pos++;
451        }
452    }
453
454    err = PREPEND_PREFIX(Dataloop_create_indexed)(cur_pos,
455                                                  tmp_blklens,
456                                                  tmp_disps,
457                                                  1, /* disp in bytes */
458                                                  oldtypes[type_pos],
459                                                  dlp_p,
460                                                  dlsz_p,
461                                                  dldepth_p,
462                                                  flag);
463
464    DLOOP_Free(tmp_blklens);
465    DLOOP_Free(tmp_disps);
466
467    return err;
468
469}
470
471static int DLOOP_Dataloop_create_basic_all_bytes_struct(
472               int count,
473               int *blklens,
474               MPI_Aint *disps,
475               DLOOP_Type *oldtypes,
476               DLOOP_Dataloop **dlp_p,
477               int *dlsz_p,
478               int *dldepth_p,
479               int flag)
480{
481    int i, err, cur_pos = 0;
482    int *tmp_blklens;
483    MPI_Aint *tmp_disps;
484
485    /* count is an upper bound on number of type instances */
486    tmp_blklens = (int *) DLOOP_Malloc(count * sizeof(int));
487
488    /* --BEGIN ERROR HANDLING-- */
489    if (!tmp_blklens)
490    {
491        return DLOOP_Dataloop_create_struct_memory_error();
492    }
493    /* --END ERROR HANDLING-- */
494
495    tmp_disps = (MPI_Aint *) DLOOP_Malloc(count * sizeof(MPI_Aint));
496
497    /* --BEGIN ERROR HANDLING-- */
498    if (!tmp_disps)
499    {
500        DLOOP_Free(tmp_blklens);
501        return DLOOP_Dataloop_create_struct_memory_error();
502    }
503    /* --END ERROR HANDLING-- */
504
505    for (i=0; i < count; i++)
506    {
507        if (oldtypes[i] != MPI_LB && oldtypes[i] != MPI_UB && blklens[i] != 0)
508        {
509            DLOOP_Offset sz;
510
511            DLOOP_Handle_get_size_macro(oldtypes[i], sz);
512            tmp_blklens[cur_pos] = (int) sz * blklens[i];
513            tmp_disps[cur_pos]   = disps[i];
514            cur_pos++;
515        }
516    }
517    err = PREPEND_PREFIX(Dataloop_create_indexed)(cur_pos,
518                                                  tmp_blklens,
519                                                  tmp_disps,
520                                                  1, /* disp in bytes */
521                                                  MPI_BYTE,
522                                                  dlp_p,
523                                                  dlsz_p,
524                                                  dldepth_p,
525                                                  flag);
526
527    DLOOP_Free(tmp_blklens);
528    DLOOP_Free(tmp_disps);
529
530    return err;
531}
532
533static int DLOOP_Dataloop_create_flattened_struct(int count,
534                                                  int *blklens,
535                                                  MPI_Aint *disps,
536                                                  DLOOP_Type *oldtypes,
537                                                  DLOOP_Dataloop **dlp_p,
538                                                  int *dlsz_p,
539                                                  int *dldepth_p,
540                                                  int flag)
541{
542    /* arbitrary types, convert to bytes and use indexed */
543    int i, err, *tmp_blklens, nr_blks = 0;
544    MPI_Aint *tmp_disps; /* since we're calling another fn that takes
545                            this type as an input parameter */
546    DLOOP_Offset bytes;
547    DLOOP_Segment *segp;
548
549    int first_ind, last_ind;
550
551    segp = PREPEND_PREFIX(Segment_alloc)();
552    /* --BEGIN ERROR HANDLING-- */
553    if (!segp) {
554        return DLOOP_Dataloop_create_struct_memory_error();
555    }
556    /* --END ERROR HANDLING-- */
557
558    /* use segment code once to count contiguous regions */
559    for (i=0; i < count; i++)
560    {
561        int is_basic;
562
563        /* ignore type elements with a zero blklen */
564        if (blklens[i] == 0) continue;
565
566        is_basic = (DLOOP_Handle_hasloop_macro(oldtypes[i])) ? 0 : 1;
567
568        if (is_basic && (oldtypes[i] != MPI_LB &&
569                         oldtypes[i] != MPI_UB))
570        {
571            nr_blks++;
572        }
573        else /* derived type; get a count of contig blocks */
574        {
575            DLOOP_Count tmp_nr_blks, sz;
576
577            DLOOP_Handle_get_size_macro(oldtypes[i], sz);
578
579            /* if the derived type has some data to contribute,
580             * add to flattened representation */
581            if (sz > 0) {
582                PREPEND_PREFIX(Segment_init)(NULL,
583                                             (DLOOP_Count) blklens[i],
584                                             oldtypes[i],
585                                             segp,
586                                             flag);
587                bytes = SEGMENT_IGNORE_LAST;
588
589                PREPEND_PREFIX(Segment_count_contig_blocks)(segp,
590                                                            0,
591                                                            &bytes,
592                                                            &tmp_nr_blks);
593
594                nr_blks += tmp_nr_blks;
595            }
596        }
597    }
598
599    /* it's possible for us to get to this point only to realize that
600     * there isn't any data in this type. in that case do what we always
601     * do: store a simple contig of zero ints and call it done.
602     */
603    if (nr_blks == 0) {
604        PREPEND_PREFIX(Segment_free)(segp);
605        err = PREPEND_PREFIX(Dataloop_create_contiguous)(0,
606                                                         MPI_INT,
607                                                         dlp_p,
608                                                         dlsz_p,
609                                                         dldepth_p,
610                                                         flag);
611        return err;
612
613    }
614
615    nr_blks += 2; /* safety measure */
616
617    tmp_blklens = (int *) DLOOP_Malloc(nr_blks * sizeof(int));
618    /* --BEGIN ERROR HANDLING-- */
619    if (!tmp_blklens) {
620        PREPEND_PREFIX(Segment_free)(segp);
621        return DLOOP_Dataloop_create_struct_memory_error();
622    }
623    /* --END ERROR HANDLING-- */
624
625
626    tmp_disps = (MPI_Aint *) DLOOP_Malloc(nr_blks * sizeof(MPI_Aint));
627    /* --BEGIN ERROR HANDLING-- */
628    if (!tmp_disps) {
629        DLOOP_Free(tmp_blklens);
630        PREPEND_PREFIX(Segment_free)(segp);
631        return DLOOP_Dataloop_create_struct_memory_error();
632    }
633    /* --END ERROR HANDLING-- */
634
635    /* use segment code again to flatten the type */
636    first_ind = 0;
637    for (i=0; i < count; i++)
638    {
639        int is_basic;
640        DLOOP_Count sz = -1;
641
642        is_basic = (DLOOP_Handle_hasloop_macro(oldtypes[i])) ? 0 : 1;
643        if (!is_basic) DLOOP_Handle_get_size_macro(oldtypes[i], sz);
644
645        /* we're going to use the segment code to flatten the type.
646         * we put in our displacement as the buffer location, and use
647         * the blocklength as the count value to get N contiguous copies
648         * of the type.
649         *
650         * Note that we're going to get back values in bytes, so that will
651         * be our new element type.
652         */
653        if (oldtypes[i] != MPI_UB &&
654            oldtypes[i] != MPI_LB &&
655            blklens[i] != 0 &&
656            (is_basic || sz > 0))
657        {
658            PREPEND_PREFIX(Segment_init)((char *) MPI_AINT_CAST_TO_VOID_PTR disps[i],
659                                         (DLOOP_Count) blklens[i],
660                                         oldtypes[i],
661                                         segp,
662                                         0 /* homogeneous */);
663
664            last_ind = nr_blks - first_ind;
665            bytes = SEGMENT_IGNORE_LAST;
666            PREPEND_PREFIX(Segment_mpi_flatten)(segp,
667                                                0,
668                                                &bytes,
669                                                &tmp_blklens[first_ind],
670                                                &tmp_disps[first_ind],
671                                                &last_ind);
672            first_ind += last_ind;
673        }
674    }
675    nr_blks = first_ind;
676
677#if 0
678    if (MPIU_DBG_SELECTED(DATATYPE,VERBOSE)) {
679        MPIU_DBG_OUT(DATATYPE,"--- start of flattened type ---");
680        for (i=0; i < nr_blks; i++) {
681        MPIU_DBG_OUT_FMT(DATATYPE,(MPIU_DBG_FDEST,
682                                   "a[%d] = (%d, " MPI_AINT_FMT_DEC_SPEC ")\n", i,
683                                   tmp_blklens[i], tmp_disps[i]));
684        }
685        MPIU_DBG_OUT(DATATYPE,"--- end of flattened type ---");
686    }
687#endif
688
689    PREPEND_PREFIX(Segment_free)(segp);
690
691    err = PREPEND_PREFIX(Dataloop_create_indexed)(nr_blks,
692                                                  tmp_blklens,
693                                                  tmp_disps,
694                                                  1, /* disp in bytes */
695                                                  MPI_BYTE,
696                                                  dlp_p,
697                                                  dlsz_p,
698                                                  dldepth_p,
699                                                  flag);
700
701    DLOOP_Free(tmp_blklens);
702    DLOOP_Free(tmp_disps);
703
704    return err;
705}
Note: See TracBrowser for help on using the browser.