Changeset 762fbc1


Ignore:
Timestamp:
Aug 15, 2020, 12:20:44 PM (16 months ago)
Author:
Peter A. Buhr <pabuhr@…>
Branches:
arm-eh, jacob/cs343-translation, master, new-ast, new-ast-unique-expr
Children:
36de20d
Parents:
7f51b9d (diff), 5715d43 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge branch 'master' of plg.uwaterloo.ca:software/cfa/cfa-cc into master

Files:
11 added
15 edited

Legend:

Unmodified
Added
Removed
  • benchmark/benchcltr.hfa

    r7f51b9d r762fbc1  
    11#pragma once
     2#include <assert.h>
     3#include <stdint.h>
    24
    3 #include <assert.h>
    4 #include <kernel.hfa>
    5 #include <thread.hfa>
    6 #include <stats.hfa>
     5#ifdef __cforall
     6        #include <kernel.hfa>
     7        #include <thread.hfa>
     8        #include <stats.hfa>
     9#else
     10#include <time.h>                                                                               // timespec
     11#include <sys/time.h>                                                                   // timeval
     12
     13enum { TIMEGRAN = 1000000000LL };                                       // nanosecond granularity, except for timeval
     14#endif
    715
    816#define BENCH_OPT_SHORT "d:p:t:SPV"
     
    1422        {"procstat",     no_argument      , 0, 'P'}, \
    1523        {"viewhalts",    no_argument      , 0, 'V'},
    16 
    17 #define BENCH_DECL \
    18         double duration = 5; \
    19         int nprocs = 1; \
    20         int nthreads = 1;
    2124
    2225#define BENCH_OPT_CASE \
     
    5255                break;
    5356
     57double duration = 5;
     58int nprocs = 1;
     59int nthreads = 1;
    5460bool silent = false;
     61bool continuous = false;
    5562bool procstats = false;
    5663bool viewhalts = false;
     64
     65#define BENCH_OPT_CFA \
     66        {'d', "duration",  "Duration of the experiments in seconds", duration }, \
     67        {'t', "nthreads",  "Number of threads to use", nthreads }, \
     68        {'p', "nprocs",    "Number of processors to use", nprocs }, \
     69        {'S', "nostats",   "Don't print statistics", silent, parse_settrue }, \
     70        {'C', "constats",  "Regularly print statistics", continuous, parse_settrue }, \
     71        {'P', "procstat",  "Print statistics for each processors", procstats, parse_settrue }, \
     72        {'V', "viewhalts", "Visualize halts, prints timestamp and Processor id for each halt.", viewhalts, parse_settrue },
     73
     74#ifdef __cforall
     75#include <parseargs.hfa>
     76
    5777struct cluster * the_benchmark_cluster = 0p;
    5878struct BenchCluster {
     
    6080};
    6181
    62 void ?{}( BenchCluster & this, int flags, int stats ) {
    63         (this.self){ "Benchmark Cluster", flags };
     82void ?{}( BenchCluster & this, int num_io, const io_context_params & io_params, int stats ) {
     83        (this.self){ "Benchmark Cluster", num_io, io_params };
    6484
    6585        assert( the_benchmark_cluster == 0p );
     
    105125        }
    106126}
     127#else
     128uint64_t getTimeNsec() {
     129        timespec curr;
     130        clock_gettime( CLOCK_REALTIME, &curr );
     131        return (int64_t)curr.tv_sec * TIMEGRAN + curr.tv_nsec;
     132}
     133
     134uint64_t to_miliseconds( uint64_t durtn ) { return durtn / (TIMEGRAN / 1000LL); }
     135double to_fseconds(uint64_t durtn ) { return durtn / (double)TIMEGRAN; }
     136uint64_t from_fseconds(double sec) { return sec * TIMEGRAN; }
     137
     138
     139void wait_duration(double duration, uint64_t & start, uint64_t & end, bool is_tty) {
     140        for(;;) {
     141                usleep(100000);
     142                end = getTimeNsec();
     143                uint64_t delta = end - start;
     144                /*if(is_tty)*/ {
     145                        printf(" %.1f\r", to_fseconds(delta));
     146                        fflush(stdout);
     147                }
     148                if( delta >= from_fseconds(duration) ) {
     149                        break;
     150                }
     151        }
     152}
     153#endif
     154
    107155
    108156void bench_usage( char * argv [] ) {
  • benchmark/io/readv.cfa

    r7f51b9d r762fbc1  
    4040int do_read(int fd, struct iovec * iov) {
    4141        // extern ssize_t cfa_preadv2(int, const struct iovec *, int, off_t, int, int = 0, Duration = -1`s, io_cancellation * = 0p, io_context * = 0p);
    42         int sflags = 0;
     42        int sflags = 0
     43        #if defined(CFA_HAVE_IOSQE_ASYNC)
     44                | CFA_IO_ASYNC
     45        #else
     46        #warning no CFA_IO_ASYNC support
     47        #endif
     48        ;
    4349        if(fixed_file) {
    4450                sflags |= CFA_IO_FIXED_FD1;
     
    6369
    6470int main(int argc, char * argv[]) {
    65         BENCH_DECL
     71        int file_flags = 0;
    6672        unsigned num_io = 1;
    67         io_context_params params;
    68         int file_flags = 0;
    6973        unsigned sublen = 16;
     74        unsigned nentries = 0;
    7075
    71         arg_loop:
    72         for(;;) {
    73                 static struct option options[] = {
    74                         BENCH_OPT_LONG
    75                         {"bufsize",       required_argument, 0, 'b'},
    76                         {"submitthread",  no_argument      , 0, 's'},
    77                         {"eagersubmit",   no_argument      , 0, 'e'},
    78                         {"kpollsubmit",   no_argument      , 0, 'k'},
    79                         {"kpollcomplete", no_argument      , 0, 'i'},
    80                         {"fixed-files",   no_argument      , 0, 'f'},
    81                         {"open-direct",   no_argument      , 0, 'o'},
    82                         {"submitlength",  required_argument, 0, 'l'},
    83                         {0, 0, 0, 0}
    84                 };
     76        bool subthrd = false;
     77        bool subeagr = false;
     78        bool odirect = false;
     79        bool kpollsb = false;
     80        bool kpollcp = false;
    8581
    86                 int idx = 0;
    87                 int opt = getopt_long(argc, argv, BENCH_OPT_SHORT "b:sekil:", options, &idx);
     82        cfa_option opt[] = {
     83                BENCH_OPT_CFA
     84                {'b', "bufsize",       "Number of bytes to read per request", buflen},
     85                {'s', "submitthread",  "If set, cluster uses polling thread to submit I/O", subthrd, parse_settrue},
     86                {'e', "eagersubmit",   "If set, cluster submits I/O eagerly but still aggregates submits", subeagr, parse_settrue},
     87                {'f', "fixed-files",   "Pre-register files with the io_contexts", fixed_file, parse_settrue},
     88                {'o', "open-direct",   "Open files with O_DIRECT flag, bypassing the file cache", odirect, parse_settrue},
     89                {'k', "kpollsubmit",   "If set, cluster uses an in kernel thread to poll submission, implies -f, requires elevated permissions", kpollsb, parse_settrue},
     90                {'i', "kpollcomplete", "If set, cluster polls fds for completions instead of relying on interrupts to get notifications, implies -o", kpollcp, parse_settrue},
     91                {'l', "submitlength",  "Size of the buffer that stores ready submissions", sublen},
     92                {'r', "numentries",    "Number of entries each of the io_context have", nentries},
     93                {'n', "numcontexts",   "Number of io_contexts to the cluster", num_io},
     94        };
     95        int opt_cnt = sizeof(opt) / sizeof(cfa_option);
    8896
    89                 const char * arg = optarg ? optarg : "";
    90                 char * end;
    91                 switch(opt) {
    92                         // Exit Case
    93                         case -1:
    94                                 break arg_loop;
    95                         BENCH_OPT_CASE
    96                         case 'b':
    97                                 buflen = strtoul(arg, &end, 10);
    98                                 if(*end != '\0' && buflen < 10) {
    99                                         fprintf(stderr, "Buffer size must be at least 10, was %s\n", arg);
    100                                         goto usage;
    101                                 }
    102                                 break;
    103                         case 's':
    104                                 params.poller_submits = true;
    105                                 break;
    106                         case 'e':
    107                                 params.eager_submits = true;
    108                                 break;
    109                         case 'k':
    110                                 params.poll_submit = true;
    111                         case 'f':
    112                                 fixed_file = true;
    113                                 break;
    114                         case 'i':
    115                                 params.poll_complete = true;
    116                         case 'o':
    117                                 file_flags |= O_DIRECT;
    118                                 break;
    119                         case 'l':
    120                                 sublen = strtoul(arg, &end, 10);
    121                                 if(*end != '\0' && sublen < 16) {
    122                                         fprintf(stderr, "Submit length must be at least 16, was %s\n", arg);
    123                                         goto usage;
    124                                 }
    125                                 // flags |= (sublen << CFA_CLUSTER_IO_BUFFLEN_OFFSET);
    126                                 break;
    127                         default: /* ? */
    128                                 fprintf(stderr, "%d\n", opt);
    129                         usage:
    130                                 bench_usage( argv );
    131                                 fprintf( stderr, "  -b, --buflen=SIZE        Number of bytes to read per request\n" );
    132                                 fprintf( stderr, "  -u, --userthread         If set, cluster uses user-thread to poll I/O\n" );
    133                                 fprintf( stderr, "  -s, --submitthread       If set, cluster uses polling thread to submit I/O\n" );
    134                                 fprintf( stderr, "  -e, --eagersubmit        If set, cluster submits I/O eagerly but still aggregates submits\n" );
    135                                 fprintf( stderr, "  -k, --kpollsubmit        If set, cluster uses IORING_SETUP_SQPOLL\n" );
    136                                 fprintf( stderr, "  -i, --kpollcomplete      If set, cluster uses IORING_SETUP_IOPOLL\n" );
    137                                 fprintf( stderr, "  -l, --submitlength=LEN   Max number of submitions that can be submitted together\n" );
    138                                 exit(EXIT_FAILURE);
     97        char **left;
     98        parse_args( opt, opt_cnt, "[OPTIONS]...\ncforall yield benchmark", left );
     99
     100        if(kpollcp || odirect) {
     101                if( (buflen % 512) != 0 ) {
     102                        fprintf(stderr, "Buffer length must be a multiple of 512 when using O_DIRECT, was %lu\n\n", buflen);
     103                        print_args_usage(opt, opt_cnt, "[OPTIONS]...\ncforall yield benchmark", true);
    139104                }
    140105        }
     106
     107        io_context_params params;
     108
     109        if( subthrd ) params.poller_submits = true;
     110        if( subeagr ) params.eager_submits  = true;
     111        if( kpollsb ) params.poll_submit    = true;
     112        if( kpollcp ) params.poll_complete  = true;
     113
     114        if(params.poll_submit  ) fixed_file = true;
     115        if(params.poll_complete) odirect    = true;
     116
     117        params.num_ready = sublen;
     118        params.num_entries = nentries;
     119
     120        if(odirect) file_flags |= O_DIRECT;
    141121
    142122        int lfd = open(__FILE__, file_flags);
  • benchmark/readyQ/yield.cfa

    r7f51b9d r762fbc1  
    4343
    4444int main(int argc, char * argv[]) {
    45         BENCH_DECL
     45        unsigned num_io = 1;
     46        io_context_params params;
    4647
    47         for(;;) {
    48                 static struct option options[] = {
    49                         BENCH_OPT_LONG
    50                         {0, 0, 0, 0}
    51                 };
     48        cfa_option opt[] = {
     49                BENCH_OPT_CFA
     50        };
     51        int opt_cnt = sizeof(opt) / sizeof(cfa_option);
    5252
    53                 int idx = 0;
    54                 int opt = getopt_long(argc, argv, BENCH_OPT_SHORT, options, &idx);
    55 
    56                 const char * arg = optarg ? optarg : "";
    57                 char * end;
    58                 switch(opt) {
    59                         case -1:
    60                                 goto run;
    61                         BENCH_OPT_CASE
    62                         default: /* ? */
    63                                 fprintf( stderr, "Unkown option '%c'\n", opt);
    64                         usage:
    65                                 bench_usage( argv );
    66                                 exit(1);
    67                 }
    68         }
    69         run:
     53        char **left;
     54        parse_args( argc, argv, opt, opt_cnt, "[OPTIONS]...\ncforall yield benchmark", left );
    7055
    7156        {
     
    7358
    7459                Time start, end;
    75                 BenchCluster cl = { 0, CFA_STATS_READY_Q };
     60                BenchCluster cl = { num_io, params, CFA_STATS_READY_Q };
    7661                {
    7762                        BenchProc procs[nprocs];
  • libcfa/src/concurrency/coroutine.cfa

    r7f51b9d r762fbc1  
    215215                return cor;
    216216        }
     217
     218        struct $coroutine * __cfactx_cor_active(void) {
     219                return active_coroutine();
     220        }
    217221}
    218222
  • libcfa/src/concurrency/invoke.c

    r7f51b9d r762fbc1  
    2929// Called from the kernel when starting a coroutine or task so must switch back to user mode.
    3030
     31extern struct $coroutine * __cfactx_cor_active(void);
    3132extern struct $coroutine * __cfactx_cor_finish(void);
    3233extern void __cfactx_cor_leave ( struct $coroutine * );
     
    3536extern void disable_interrupts() OPTIONAL_THREAD;
    3637extern void enable_interrupts( __cfaabi_dbg_ctx_param );
     38
     39struct exception_context_t * this_exception_context() {
     40        return &__get_stack( __cfactx_cor_active() )->exception_context;
     41}
    3742
    3843void __cfactx_invoke_coroutine(
  • libcfa/src/concurrency/invoke.h

    r7f51b9d r762fbc1  
    9898        }
    9999
     100        struct exception_context_t * this_exception_context();
     101
    100102        // struct which calls the monitor is accepting
    101103        struct __waitfor_mask_t {
  • libcfa/src/concurrency/io.cfa

    r7f51b9d r762fbc1  
    359359
    360360                        // We got the lock
     361                        // Collect the submissions
    361362                        unsigned to_submit = __collect_submitions( ring );
     363
     364                        // Actually submit
    362365                        int ret = __io_uring_enter( ring, to_submit, false );
    363                         if( ret < 0 ) {
    364                                 unlock(ring.submit_q.lock);
    365                                 return;
    366                         }
    367 
    368                         /* paranoid */ verify( ret > 0 || to_submit == 0 || (ring.ring_flags & IORING_SETUP_SQPOLL) );
     366
     367                        unlock(ring.submit_q.lock);
     368                        if( ret < 0 ) return;
    369369
    370370                        // Release the consumed SQEs
     
    372372
    373373                        // update statistics
    374                         __STATS__( true,
     374                        __STATS__( false,
    375375                                io.submit_q.submit_avg.rdy += to_submit;
    376376                                io.submit_q.submit_avg.csm += ret;
    377377                                io.submit_q.submit_avg.cnt += 1;
    378378                        )
    379 
    380                         unlock(ring.submit_q.lock);
    381379                }
    382380                else {
  • libcfa/src/concurrency/io/setup.cfa

    r7f51b9d r762fbc1  
    298298                if( params_in.poll_complete ) params.flags |= IORING_SETUP_IOPOLL;
    299299
    300                 uint32_t nentries = params_in.num_entries;
     300                uint32_t nentries = params_in.num_entries != 0 ? params_in.num_entries : 256;
     301                if( !is_pow2(nentries) ) {
     302                        abort("ERROR: I/O setup 'num_entries' must be a power of 2\n");
     303                }
     304                if( params_in.poller_submits && params_in.eager_submits ) {
     305                        abort("ERROR: I/O setup 'poller_submits' and 'eager_submits' cannot be used together\n");
     306                }
    301307
    302308                int fd = syscall(__NR_io_uring_setup, nentries, &params );
  • libcfa/src/concurrency/iocall.cfa

    r7f51b9d r762fbc1  
    101101        #endif
    102102
    103 
    104103        #define __submit_prelude \
    105104                if( 0 != (submit_flags & LINK_FLAGS) ) { errno = ENOTSUP; return -1; } \
     
    110109                struct io_uring_sqe * sqe; \
    111110                uint32_t idx; \
     111                uint8_t sflags = REGULAR_FLAGS & submit_flags; \
    112112                [sqe, idx] = __submit_alloc( ring, (uint64_t)(uintptr_t)&data ); \
    113                 sqe->flags = REGULAR_FLAGS & submit_flags;
     113                sqe->flags = sflags;
    114114
    115115        #define __submit_wait \
     
    186186                        __submit_prelude
    187187
    188                         (*sqe){ IORING_OP_READV, fd, iov, iovcnt, offset };
     188                        sqe->opcode = IORING_OP_READV;
     189                        sqe->ioprio = 0;
     190                        sqe->fd = fd;
     191                        sqe->off = offset;
     192                        sqe->addr = (uint64_t)(uintptr_t)iov;
     193                        sqe->len = iovcnt;
     194                        sqe->rw_flags = 0;
     195                        sqe->__pad2[0] = sqe->__pad2[1] = sqe->__pad2[2] = 0;
    189196
    190197                        __submit_wait
  • libcfa/src/exception.c

    r7f51b9d r762fbc1  
    5959
    6060
    61 // Temperary global exception context. Does not work with concurency.
    62 static struct exception_context_t shared_stack = {NULL, NULL};
    63 
    6461// Get the current exception context.
    6562// There can be a single global until multithreading occurs, then each stack
    66 // needs its own. It will have to be updated to handle that.
    67 struct exception_context_t * this_exception_context() {
     63// needs its own. We get this from libcfathreads (no weak attribute).
     64__attribute__((weak)) struct exception_context_t * this_exception_context() {
     65        static struct exception_context_t shared_stack = {NULL, NULL};
    6866        return &shared_stack;
    6967}
  • libcfa/src/parseargs.cfa

    r7f51b9d r762fbc1  
    1919        extern          long long int strtoll (const char* str, char** endptr, int base);
    2020        extern unsigned long long int strtoull(const char* str, char** endptr, int base);
     21        extern                 double strtod  (const char* str, char** endptr);
    2122}
    2223
     
    2829extern char ** cfa_args_envp;
    2930
    30 void printopt(FILE * out, int width, int max, char sn, const char * ln, const char * help) {
    31         int hwidth = max - (11 + width);
    32         if(hwidth <= 0) hwidth = max;
    33 
    34         fprintf(out, "  -%c, --%-*s   %.*s\n", sn, width, ln, hwidth, help);
    35         for() {
    36                 help += min(strlen(help), hwidth);
    37                 if('\0' == *help) break;
    38                 fprintf(out, "%*s%.*s\n", width + 11, "", hwidth, help);
    39         }
    40 }
     31static void usage(char * cmd, cfa_option options[], size_t opt_count, const char * usage, FILE * out)  __attribute__ ((noreturn));
    4132
    4233void parse_args( cfa_option options[], size_t opt_count, const char * usage, char ** & left ) {
     
    4435}
    4536
     37//-----------------------------------------------------------------------------
     38// getopt_long wrapping
    4639void parse_args(
    4740        int argc,
     
    5346) {
    5447        struct option optarr[opt_count + 2];
    55         int width = 0;
    56         int max_width = 1_000_000;
    5748        {
    5849                int idx = 0;
     
    6960                                }
    7061                                idx++;
    71 
    72                                 int w = strlen(options[i].long_name);
    73                                 if(w > width) width = w;
    7462                        }
    7563                }
     
    10694                                out = stdout;
    10795                        case '?':
    108                                 goto USAGE;
     96                                usage(argv[0], options, opt_count, usage, out);
    10997                        default:
    11098                                for(i; opt_count) {
     
    115103
    116104                                                fprintf(out, "Argument '%s' for option %c could not be parsed\n\n", arg, (char)opt);
    117                                                 goto USAGE;
     105                                                usage(argv[0], options, opt_count, usage, out);
    118106                                        }
    119107                                }
     
    122110
    123111        }
    124 
    125         USAGE:;
     112}
     113
     114//-----------------------------------------------------------------------------
     115// Print usage
     116static void printopt(FILE * out, int width, int max, char sn, const char * ln, const char * help) {
     117        int hwidth = max - (11 + width);
     118        if(hwidth <= 0) hwidth = max;
     119
     120        fprintf(out, "  -%c, --%-*s   %.*s\n", sn, width, ln, hwidth, help);
     121        for() {
     122                help += min(strlen(help), hwidth);
     123                if('\0' == *help) break;
     124                fprintf(out, "%*s%.*s\n", width + 11, "", hwidth, help);
     125        }
     126}
     127
     128void print_args_usage(cfa_option options[], size_t opt_count, const char * usage, bool error)  __attribute__ ((noreturn)) {
     129        usage(cfa_args_argv[0], options, opt_count, usage, error ? stderr : stdout);
     130}
     131
     132void print_args_usage(int , char * argv[], cfa_option options[], size_t opt_count, const char * usage, bool error)  __attribute__ ((noreturn)) {
     133        usage(argv[0], options, opt_count, usage, error ? stderr : stdout);
     134}
     135
     136static void usage(char * cmd, cfa_option options[], size_t opt_count, const char * help, FILE * out) __attribute__((noreturn)) {
     137        int width = 0;
     138        {
     139                int idx = 0;
     140                for(i; opt_count) {
     141                        if(options[i].long_name) {
     142                                int w = strlen(options[i].long_name);
     143                                if(w > width) width = w;
     144                        }
     145                }
     146        }
     147
     148        int max_width = 1_000_000;
    126149        int outfd = fileno(out);
    127150        if(isatty(outfd)) {
     
    132155        }
    133156
    134         fprintf(out, "Usage:\n  %s %s\n", argv[0], usage);
     157        fprintf(out, "Usage:\n  %s %s\n", cmd, help);
    135158
    136159        for(i; opt_count) {
     
    141164}
    142165
     166//-----------------------------------------------------------------------------
     167// Typed argument parsing
    143168bool parse_yesno(const char * arg, bool & value ) {
    144169        if(strcmp(arg, "yes") == 0) {
     
    167192bool parse(const char * arg, const char * & value ) {
    168193        value = arg;
     194        return true;
     195}
     196
     197bool parse(const char * arg, int & value) {
     198        char * end;
     199        int r = strtoll(arg, &end, 10);
     200        if(*end != '\0') return false;
     201
     202        value = r;
    169203        return true;
    170204}
     
    200234}
    201235
    202 bool parse(const char * arg, int & value) {
    203         char * end;
    204         int r = strtoll(arg, &end, 10);
    205         if(*end != '\0') return false;
    206 
    207         value = r;
    208         return true;
    209 }
     236bool parse(const char * arg, double & value) {
     237        char * end;
     238        double r = strtod(arg, &end);
     239        if(*end != '\0') return false;
     240
     241        value = r;
     242        return true;
     243}
  • libcfa/src/parseargs.hfa

    r7f51b9d r762fbc1  
    3434void parse_args( int argc, char * argv[], cfa_option options[], size_t opt_count, const char * usage, char ** & left );
    3535
     36void print_args_usage(cfa_option options[], size_t opt_count, const char * usage, bool error)  __attribute__ ((noreturn));
     37void print_args_usage(int argc, char * argv[], cfa_option options[], size_t opt_count, const char * usage, bool error)  __attribute__ ((noreturn));
     38
    3639bool parse_yesno   (const char *, bool & );
    3740bool parse_settrue (const char *, bool & );
     
    3942
    4043bool parse(const char *, const char * & );
     44bool parse(const char *, int & );
    4145bool parse(const char *, unsigned & );
    4246bool parse(const char *, unsigned long & );
    4347bool parse(const char *, unsigned long long & );
    44 bool parse(const char *, int & );
     48bool parse(const char *, double & );
  • tests/Makefile.am

    r7f51b9d r762fbc1  
    163163        $(CFACOMPILETEST) -DERR2 -c -fsyntax-only -o $(abspath ${@})
    164164
     165# Exception Tests
     166# Test with libcfathread; it changes how storage works.
     167
     168exceptions/%-threads : exceptions/%.cfa $(CFACCBIN)
     169        $(CFACOMPILETEST) -include exceptions/with-threads.hfa -c -o $(abspath ${@}).o
     170        $(CFACCLOCAL) $($(shell echo "${@}_FLAGSLD" | sed 's/-\|\//_/g')) $(abspath ${@}).o -o $(abspath ${@})
     171
    165172#------------------------------------------------------------------------------
    166173# Other targets
  • tests/exceptions/terminate.cfa

    r7f51b9d r762fbc1  
    142142        }
    143143}
    144 
  • tests/linking/withthreads.cfa

    r7f51b9d r762fbc1  
    55// file "LICENCE" distributed with Cforall.
    66//
    7 // nothreads.cfa --
     7// withthreads.cfa --
    88//
    99// Author           : Thierry Delisle
Note: See TracChangeset for help on using the changeset viewer.