source: soft/giet_vm/applications/transpose/main.c @ 706

Last change on this file since 706 was 669, checked in by alain, 9 years ago

Introduce support for the "shared" argument in the giet_tty_alloc() system call,
and replace the giet_shr_printf() system call by giet_tty_printf().

File size: 21.7 KB
RevLine 
[502]1///////////////////////////////////////////////////////////////////////////////////////
[295]2// File   : main.c   (for transpose application)
3// Date   : february 2014
4// author : Alain Greiner
[502]5///////////////////////////////////////////////////////////////////////////////////////
[589]6// This multi-threaded application makes a transpose for a NN*NN pixels image.
[444]7// It can run on a multi-processors, multi-clusters architecture, with one thread
[502]8// per processor.
9//
[589]10// The image is read from a file (one byte per pixel), transposed and
11// saved in a second file. Then the transposed image is read from the second file,
12// transposed again and saved in a third file.
13//
[295]14// The input and output buffers containing the image are distributed in all clusters.
15//
[589]16// - The image size NN must fit the frame buffer size.
[502]17// - The block size in block device must be 512 bytes.
[589]18// - The number of clusters  must be a power of 2 no larger than 64.
19// - The number of processors per cluster must be a power of 2 no larger than 4.
[295]20//
21// For each image the application makes a self test (checksum for each line).
[393]22// The actual display on the frame buffer depends on frame buffer availability.
[502]23///////////////////////////////////////////////////////////////////////////////////////
[295]24
25#include "stdio.h"
[502]26#include "user_barrier.h"
[383]27#include "malloc.h"
[295]28
[589]29#define BLOCK_SIZE            512                         // block size on disk
30#define X_MAX                 8                           // max number of clusters in row
31#define Y_MAX                 8                           // max number of clusters in column
32#define PROCS_MAX             4                           // max number of procs per cluster
33#define CLUSTER_MAX           (X_MAX * Y_MAX)             // max number of clusters
34#define NN                    256                         // image size : nlines = npixels
[669]35#define INITIAL_FILE_PATH     "/misc/lena_256.raw"        // pathname on virtual disk
[589]36#define TRANSPOSED_FILE_PATH  "/home/lena_transposed.raw" // pathname on virtual disk
37#define RESTORED_FILE_PATH    "/home/lena_restored.raw"   // pathname on virtual disk
38#define INSTRUMENTATION_OK    1                           // display statistics on TTY
[295]39
[669]40// macro to use a shared TTY
41#define printf(...)     lock_acquire( &tty_lock ); \
42                        giet_tty_printf(__VA_ARGS__);  \
43                        lock_release( &tty_lock )
44
[295]45///////////////////////////////////////////////////////
46// global variables stored in seg_data in cluster(0,0)
47///////////////////////////////////////////////////////
48
[502]49// instrumentation counters for each processor in each cluster
[589]50unsigned int LOAD_START[X_MAX][Y_MAX][PROCS_MAX] = {{{ 0 }}};
51unsigned int LOAD_END  [X_MAX][Y_MAX][PROCS_MAX] = {{{ 0 }}};
52unsigned int TRSP_START[X_MAX][Y_MAX][PROCS_MAX] = {{{ 0 }}};
53unsigned int TRSP_END  [X_MAX][Y_MAX][PROCS_MAX] = {{{ 0 }}};
54unsigned int DISP_START[X_MAX][Y_MAX][PROCS_MAX] = {{{ 0 }}};
55unsigned int DISP_END  [X_MAX][Y_MAX][PROCS_MAX] = {{{ 0 }}};
56unsigned int STOR_START[X_MAX][Y_MAX][PROCS_MAX] = {{{ 0 }}};
57unsigned int STOR_END  [X_MAX][Y_MAX][PROCS_MAX] = {{{ 0 }}};
[295]58
59// arrays of pointers on distributed buffers
60// one input buffer & one output buffer per cluster
[589]61unsigned char*  buf_in [CLUSTER_MAX];
62unsigned char*  buf_out[CLUSTER_MAX];
[295]63
64// checksum variables
65unsigned check_line_before[NN];
66unsigned check_line_after[NN];
67
[669]68// lock protecting shared TTY
69user_lock_t  tty_lock;
70
71// global & local synchronisation variables
[502]72giet_sqt_barrier_t barrier;
[295]73
[589]74volatile unsigned int global_init_ok = 0;
75volatile unsigned int local_init_ok[X_MAX][Y_MAX] = {{ 0 }};
[295]76
77//////////////////////////////////////////
78__attribute__ ((constructor)) void main()
[383]79//////////////////////////////////////////
[295]80{
[502]81    unsigned int l;                  // line index for loops
82    unsigned int p;                  // pixel index for loops
[295]83
[502]84    // processor identifiers
85    unsigned int x;                  // x cluster coordinate
86    unsigned int y;                  // y cluster coordinate
87    unsigned int lpid;               // local processor index
88
89    // plat-form parameters
90    unsigned int x_size;             // number of clusters in a row
91    unsigned int y_size;             // number of clusters in a column
92    unsigned int nprocs;             // number of processors per cluster
93   
[432]94    giet_proc_xyp( &x, &y, &lpid);             
[295]95
[502]96    giet_procs_number( &x_size , &y_size , &nprocs );
[295]97
[589]98    unsigned int nclusters     = x_size * y_size;               // number of clusters
99    unsigned int ntasks        = x_size * y_size * nprocs;      // number of tasks
100    unsigned int npixels       = NN * NN;                       // pixels per image
101    unsigned int iteration     = 0;                             // iiteration iter
102    int          fd_initial    = 0;                             // initial file descriptor
103    int          fd_transposed = 0;                             // transposed file descriptor
104    int          fd_restored   = 0;                             // restored file descriptor
105    unsigned int cluster_id    = (x * y_size) + y;              // "continuous" index   
106    unsigned int task_id       = (cluster_id * nprocs) + lpid;  // "continuous" task index
[502]107
[669]108    // checking parameters
109    giet_assert( ((nprocs == 1) || (nprocs == 2) || (nprocs == 4)),
110                 "[TRANSPOSE ERROR] number of procs per cluster must be 1, 2 or 4");
[589]111
[669]112    giet_assert( ((x_size == 1) || (x_size == 2) || (x_size == 4) || 
113                  (x_size == 8) || (x_size == 16)),
114                 "[TRANSPOSE ERROR] x_size must be 1,2,4,8,16");
115
116    giet_assert( ((y_size == 1) || (y_size == 2) || (y_size == 4) || 
117                  (y_size == 8) || (y_size == 16)),
118                 "[TRANSPOSE ERROR] y_size must be 1,2,4,8,16");
119
120    giet_assert( (ntasks <= NN ),
121                 "[TRANSPOSE ERROR] number of tasks larger than number of lines");
122
[589]123    ///////////////////////////////////////////////////////////////////////
124    // Processor [0,0,0] makes global initialisation
125    // It includes parameters checking, heap and barrier initialization.
126    // Others processors wait initialisation completion
127    ///////////////////////////////////////////////////////////////////////
128
[432]129    if ( (x==0) && (y==0) && (lpid==0) )
[295]130    {
[669]131        // shared TTY allocation
132        giet_tty_alloc( 1 );
[295]133
[669]134        // TTY lock initialisation
135        lock_init( &tty_lock);
136     
[589]137        // distributed heap initialisation
138        unsigned int cx , cy;
139        for ( cx = 0 ; cx < x_size ; cx++ ) 
[393]140        {
[589]141            for ( cy = 0 ; cy < y_size ; cy++ ) 
142            {
143                heap_init( cx , cy );
144            }
[393]145        }
[295]146
[589]147        // barrier initialisation
[543]148        sqt_barrier_init( &barrier, x_size , y_size , nprocs );
149
[669]150        printf("\n[TRANSPOSE] Proc [0,0,0] completes initialisation at cycle %d\n",
151               giet_proctime() );
[543]152
[589]153        global_init_ok = 1;
154    }
155    else 
156    {
157        while ( global_init_ok == 0 );
158    }
159   
160    ///////////////////////////////////////////////////////////////////////
161    // In each cluster, only task running on processor[x,y,0] allocates
162    // the local buffers containing the images in the distributed heap
163    // (one buf_in and one buf_out per cluster).
164    // Other processors in cluster wait completion.
165    ///////////////////////////////////////////////////////////////////////
166
167    if ( lpid == 0 ) 
168    {
169        buf_in[cluster_id]  = remote_malloc( npixels/nclusters, x, y );
170        buf_out[cluster_id] = remote_malloc( npixels/nclusters, x, y );
171
172        if ( (x==0) && (y==0) )
[669]173        printf("\n[TRANSPOSE] Proc [%d,%d,%d] completes buffer allocation"
174               " for cluster[%d,%d] at cycle %d\n"
175               " - buf_in  = %x\n"
176               " - buf_out = %x\n",
177               x, y, lpid, x, y, giet_proctime(), 
178               (unsigned int)buf_in[cluster_id], (unsigned int)buf_out[cluster_id] );
[589]179
180        ///////////////////////////////////////////////////////////////////////
181        // In each cluster, only task running on procesor[x,y,0] open the
182        // three private file descriptors for the three files
183        ///////////////////////////////////////////////////////////////////////
184
185        // open initial file
186        fd_initial = giet_fat_open( INITIAL_FILE_PATH , O_RDONLY );  // read_only
187        if ( fd_initial < 0 ) 
[307]188        { 
[669]189            printf("\n[TRANSPOSE ERROR] Proc [%d,%d,%d] cannot open file %s\n",
190                   x , y , lpid , INITIAL_FILE_PATH );
[307]191            giet_exit(" open() failure");
192        }
[589]193        else if ( (x==0) && (y==0) && (lpid==0) )
[317]194        {
[669]195            printf("\n[TRANSPOSE] Proc [0,0,0] open file %s / fd = %d\n",
196                   INITIAL_FILE_PATH , fd_initial );
[317]197        }
[589]198
199        // open transposed file
200        fd_transposed = giet_fat_open( TRANSPOSED_FILE_PATH , O_CREATE );   // create if required
201        if ( fd_transposed < 0 ) 
202        { 
[669]203            printf("\n[TRANSPOSE ERROR] Proc [%d,%d,%d] cannot open file %s\n",
[589]204                            x , y , lpid , TRANSPOSED_FILE_PATH );
205            giet_exit(" open() failure");
206        }
207        else if ( (x==0) && (y==0) && (lpid==0) )
208        {
[669]209            printf("\n[TRANSPOSE] Proc [0,0,0] open file %s / fd = %d\n",
210                   TRANSPOSED_FILE_PATH , fd_transposed );
[589]211        }
212
213        // open restored file
214        fd_restored = giet_fat_open( RESTORED_FILE_PATH , O_CREATE );   // create if required
215        if ( fd_restored < 0 ) 
216        { 
[669]217            printf("\n[TRANSPOSE ERROR] Proc [%d,%d,%d] cannot open file %s\n",
218                   x , y , lpid , RESTORED_FILE_PATH );
[589]219            giet_exit(" open() failure");
220        }
221        else if ( (x==0) && (y==0) && (lpid==0) )
222        {
[669]223            printf("\n[TRANSPOSE] Proc [0,0,0] open file %s / fd = %d\n",
224                   RESTORED_FILE_PATH , fd_restored );
[589]225        }
226
227        local_init_ok[x][y] = 1;
[295]228    }
[589]229    else
[393]230    {
[589]231        while( local_init_ok[x][y] == 0 );
[393]232    }
[589]233
234    ///////////////////////////////////////////////////////////////////////
235    // Main loop / two iterations:
236    // - first makes  initial    => transposed
237    // - second makes transposed => restored
238    // All processors execute this main loop.
239    ///////////////////////////////////////////////////////////////////////
240
241    unsigned int fd_in  = fd_initial;
242    unsigned int fd_out = fd_transposed;
243
244    while (iteration < 2)
[295]245    {
[589]246        ///////////////////////////////////////////////////////////////////////
247        // pseudo parallel load from disk to buf_in buffers: npixels/nclusters
248        // only task running on processor(x,y,0) does it
249        ///////////////////////////////////////////////////////////////////////
[295]250
[589]251        LOAD_START[x][y][lpid] = giet_proctime();
[295]252
253        if (lpid == 0)
254        {
[589]255            unsigned int offset = ((npixels*cluster_id)/nclusters);
256            if ( giet_fat_lseek( fd_in,
257                                 offset,
258                                 SEEK_SET ) != offset )
259            {
[669]260                printf("\n[TRANSPOSE ERROR] Proc [%d,%d,%d] cannot seek fd = %d\n",
261                       x , y , lpid , fd_in );
[589]262                giet_exit(" seek() failure");
263            }
[295]264
[589]265            unsigned int pixels = npixels / nclusters;
266            if ( giet_fat_read( fd_in,
267                                buf_in[cluster_id],
268                                pixels ) != pixels )
269            {
[669]270                printf("\n[TRANSPOSE ERROR] Proc [%d,%d,%d] cannot read fd = %d\n",
271                       x , y , lpid , fd_in );
[589]272                giet_exit(" read() failure");
273            }
274
[502]275            if ( (x==0) && (y==0) )
[669]276            printf("\n[TRANSPOSE] Proc [%d,%d,%d] completes load"
277                   "  for iteration %d at cycle %d\n",
278                   x, y, lpid, iteration, giet_proctime() );
[295]279        }
280
[589]281        LOAD_END[x][y][lpid] = giet_proctime();
[295]282
[502]283        /////////////////////////////
284        sqt_barrier_wait( &barrier );
[295]285
[589]286        ///////////////////////////////////////////////////////////////////////
[295]287        // parallel transpose from buf_in to buf_out
288        // each task makes the transposition for nlt lines (nlt = NN/ntasks)
289        // from line [task_id*nlt] to line [(task_id + 1)*nlt - 1]
290        // (p,l) are the absolute pixel coordinates in the source image
[589]291        ///////////////////////////////////////////////////////////////////////
[295]292
[589]293        TRSP_START[x][y][lpid] = giet_proctime();
[295]294
295        unsigned int nlt   = NN / ntasks;      // number of lines per task
[502]296        unsigned int nlc   = NN / nclusters;   // number of lines per cluster
[295]297
298        unsigned int src_cluster;
299        unsigned int src_index;
300        unsigned int dst_cluster;
301        unsigned int dst_index;
302
303        unsigned char byte;
304
305        unsigned int first = task_id * nlt;    // first line index for a given task
306        unsigned int last  = first + nlt;      // last line index for a given task
307
308        for ( l = first ; l < last ; l++ )
309        {
310            check_line_before[l] = 0;
311         
312            // in each iteration we transfer one byte
313            for ( p = 0 ; p < NN ; p++ )
314            {
315                // read one byte from local buf_in
316                src_cluster = l / nlc;
317                src_index   = (l % nlc)*NN + p;
318                byte        = buf_in[src_cluster][src_index];
319
320                // compute checksum
321                check_line_before[l] = check_line_before[l] + byte;
322
323                // write one byte to remote buf_out
324                dst_cluster = p / nlc; 
325                dst_index   = (p % nlc)*NN + l;
326                buf_out[dst_cluster][dst_index] = byte;
327            }
328        }
329
[669]330//        if ( lpid == 0 )
[307]331        {
[669]332//            if ( (x==0) && (y==0) )
333            printf("\n[TRANSPOSE] proc [%d,%d,0] completes transpose"
334                   " for iteration %d at cycle %d\n", 
335                   x, y, iteration, giet_proctime() );
[295]336
[307]337        }
[589]338        TRSP_END[x][y][lpid] = giet_proctime();
[295]339
[502]340        /////////////////////////////
341        sqt_barrier_wait( &barrier );
[295]342
[589]343        ///////////////////////////////////////////////////////////////////////
344        // parallel display from local buf_out to frame buffer
345        // all tasks contribute to display using memcpy...
346        ///////////////////////////////////////////////////////////////////////
[295]347
[589]348        DISP_START[x][y][lpid] = giet_proctime();
[515]349
[589]350        unsigned int  npt   = npixels / ntasks;   // number of pixels per task
[295]351
[589]352        giet_fbf_sync_write( npt * task_id, 
353                             &buf_out[cluster_id][lpid*npt], 
354                             npt );
[295]355
[669]356//        if ( (x==0) && (y==0) && (lpid==0) )
357        printf("\n[TRANSPOSE] Proc [%d,%d,%d] completes display"
358               " for iteration %d at cycle %d\n",
359               x, y, lpid, iteration, giet_proctime() );
[295]360
[589]361        DISP_END[x][y][lpid] = giet_proctime();
[295]362
[589]363        /////////////////////////////
364        sqt_barrier_wait( &barrier );
[295]365
[589]366        ///////////////////////////////////////////////////////////////////////
367        // pseudo parallel store : buf_out buffers to disk : npixels/nclusters
368        // only task running on processor(x,y,0) does it
369        ///////////////////////////////////////////////////////////////////////
370
371        STOR_START[x][y][lpid] = giet_proctime();
372
373        if ( lpid == 0 )
[295]374        {
[589]375            unsigned int offset = ((npixels*cluster_id)/nclusters);
376            if ( giet_fat_lseek( fd_out,
377                                 offset,
378                                 SEEK_SET ) != offset )
[295]379            {
[669]380                printf("\n[TRANSPOSE ERROR] Proc [%d,%d,%d] cannot seek fr = %d\n",
381                       x , y , lpid , fd_out );
[589]382                giet_exit(" seek() failure");
383            }
[295]384
[589]385            unsigned int pixels = npixels / nclusters;
386            if ( giet_fat_write( fd_out,
387                                 buf_out[cluster_id],
388                                 pixels ) != pixels )
389            {
[669]390                printf("\n[TRANSPOSE ERROR] Proc [%d,%d,%d] cannot write fd = %d\n",
391                       x , y , lpid , fd_out );
[589]392                giet_exit(" write() failure");
393            }
[295]394
[589]395            if ( (x==0) && (y==0) )
[669]396            printf("\n[TRANSPOSE] Proc [%d,%d,%d] completes store"
397                   "  for iteration %d at cycle %d\n",
398                   x, y, lpid, iteration, giet_proctime() );
[589]399        }
[295]400
[589]401        STOR_END[x][y][lpid] = giet_proctime();
[515]402
[502]403        /////////////////////////////
404        sqt_barrier_wait( &barrier );
[336]405
[432]406        // instrumentation done by processor [0,0,0]
407        if ( (x==0) && (y==0) && (lpid==0) && INSTRUMENTATION_OK )
[295]408        {
[589]409            int cx , cy , pp ;
[295]410            unsigned int min_load_start = 0xFFFFFFFF;
411            unsigned int max_load_start = 0;
412            unsigned int min_load_ended = 0xFFFFFFFF;
413            unsigned int max_load_ended = 0;
414            unsigned int min_trsp_start = 0xFFFFFFFF;
415            unsigned int max_trsp_start = 0;
416            unsigned int min_trsp_ended = 0xFFFFFFFF;
417            unsigned int max_trsp_ended = 0;
418            unsigned int min_disp_start = 0xFFFFFFFF;
419            unsigned int max_disp_start = 0;
420            unsigned int min_disp_ended = 0xFFFFFFFF;
421            unsigned int max_disp_ended = 0;
[589]422            unsigned int min_stor_start = 0xFFFFFFFF;
423            unsigned int max_stor_start = 0;
424            unsigned int min_stor_ended = 0xFFFFFFFF;
425            unsigned int max_stor_ended = 0;
[295]426
[589]427            for (cx = 0; cx < x_size; cx++)
[295]428            {
[589]429            for (cy = 0; cy < y_size; cy++)
430            {
431            for (pp = 0; pp < NB_PROCS_MAX; pp++)
432            {
433                if (LOAD_START[cx][cy][pp] < min_load_start)  min_load_start = LOAD_START[cx][cy][pp];
434                if (LOAD_START[cx][cy][pp] > max_load_start)  max_load_start = LOAD_START[cx][cy][pp];
435                if (LOAD_END[cx][cy][pp]   < min_load_ended)  min_load_ended = LOAD_END[cx][cy][pp]; 
436                if (LOAD_END[cx][cy][pp]   > max_load_ended)  max_load_ended = LOAD_END[cx][cy][pp];
437                if (TRSP_START[cx][cy][pp] < min_trsp_start)  min_trsp_start = TRSP_START[cx][cy][pp];
438                if (TRSP_START[cx][cy][pp] > max_trsp_start)  max_trsp_start = TRSP_START[cx][cy][pp];
439                if (TRSP_END[cx][cy][pp]   < min_trsp_ended)  min_trsp_ended = TRSP_END[cx][cy][pp];
440                if (TRSP_END[cx][cy][pp]   > max_trsp_ended)  max_trsp_ended = TRSP_END[cx][cy][pp];
441                if (DISP_START[cx][cy][pp] < min_disp_start)  min_disp_start = DISP_START[cx][cy][pp];
442                if (DISP_START[cx][cy][pp] > max_disp_start)  max_disp_start = DISP_START[cx][cy][pp];
443                if (DISP_END[cx][cy][pp]   < min_disp_ended)  min_disp_ended = DISP_END[cx][cy][pp];
444                if (DISP_END[cx][cy][pp]   > max_disp_ended)  max_disp_ended = DISP_END[cx][cy][pp];
445                if (STOR_START[cx][cy][pp] < min_stor_start)  min_stor_start = STOR_START[cx][cy][pp];
446                if (STOR_START[cx][cy][pp] > max_stor_start)  max_stor_start = STOR_START[cx][cy][pp];
447                if (STOR_END[cx][cy][pp]   < min_stor_ended)  min_stor_ended = STOR_END[cx][cy][pp];
448                if (STOR_END[cx][cy][pp]   > max_stor_ended)  max_stor_ended = STOR_END[cx][cy][pp];
[295]449            }
[589]450            }
451            }
[295]452
[669]453            printf("\n   ---------------- Instrumentation Results ---------------------\n");
[589]454
[669]455            printf(" - LOAD_START : min = %d / max = %d / med = %d / delta = %d\n",
456                   min_load_start, max_load_start, (min_load_start+max_load_start)/2, 
457                   max_load_start-min_load_start); 
[295]458
[669]459            printf(" - LOAD_END   : min = %d / max = %d / med = %d / delta = %d\n",
460                   min_load_ended, max_load_ended, (min_load_ended+max_load_ended)/2, 
461                   max_load_ended-min_load_ended); 
[295]462
[669]463            printf(" - TRSP_START : min = %d / max = %d / med = %d / delta = %d\n",
464                   min_trsp_start, max_trsp_start, (min_trsp_start+max_trsp_start)/2, 
465                   max_trsp_start-min_trsp_start); 
[295]466
[669]467            printf(" - TRSP_END   : min = %d / max = %d / med = %d / delta = %d\n",
468                   min_trsp_ended, max_trsp_ended, (min_trsp_ended+max_trsp_ended)/2, 
469                   max_trsp_ended-min_trsp_ended); 
[295]470
[669]471            printf(" - DISP_START : min = %d / max = %d / med = %d / delta = %d\n",
472                   min_disp_start, max_disp_start, (min_disp_start+max_disp_start)/2, 
473                   max_disp_start-min_disp_start); 
[295]474
[669]475            printf(" - DISP_END   : min = %d / max = %d / med = %d / delta = %d\n",
476                   min_disp_ended, max_disp_ended, (min_disp_ended+max_disp_ended)/2, 
477                   max_disp_ended-min_disp_ended); 
[589]478
[669]479            printf(" - STOR_START : min = %d / max = %d / med = %d / delta = %d\n",
480                   min_stor_start, max_stor_start, (min_stor_start+max_stor_start)/2, 
481                   max_stor_start-min_stor_start); 
[589]482
[669]483            printf(" - STOR_END   : min = %d / max = %d / med = %d / delta = %d\n",
484                   min_stor_ended, max_stor_ended, (min_stor_ended+max_stor_ended)/2, 
485                   max_stor_ended-min_stor_ended); 
[295]486        }
487
[502]488        /////////////////////////////
489        sqt_barrier_wait( &barrier );
[295]490
[589]491        // update iteration variables
492        fd_in  = fd_transposed;
493        fd_out = fd_restored;
494        iteration++;
[295]495
[589]496    } // end while     
497
498    ///////////////////////////////////////////////////////////////////////
499    // In each cluster, only task running on Processor[x,y,0] releases
500    // the distributed buffers and close the file descriptors.
501    ///////////////////////////////////////////////////////////////////////
502
503    if ( lpid==0 )
[398]504    {
[589]505        free( buf_in[cluster_id] );
506        free( buf_out[cluster_id] );
507
508        giet_fat_close( fd_initial );
509        giet_fat_close( fd_transposed );
510        giet_fat_close( fd_restored );
[398]511    }
512
[295]513    giet_exit("Completed");
514
515} // end main()
516
517// Local Variables:
518// tab-width: 3
519// c-basic-offset:
520// c-file-offsets:((innamespace . 0)(inline-open . 0))
521// indent-tabs-mode: nil
522// End:
523
524// vim: filetype=cpp:expandtab:shiftwidth=3:tabstop=3:softtabstop=3
525
526
527
Note: See TracBrowser for help on using the repository browser.