source: sources/src/gen_code.cc @ 62

Last change on this file since 62 was 62, checked in by meunier, 7 years ago
  • Functional (or supposedly functional) OpenMP support configure must be run with --enable-use-omp and the topcell must define the USE_OPENMP flag before including the .h files of systemcass (if openmp enabled).
File size: 20.8 KB
Line 
1/*------------------------------------------------------------\
2  |                                                             |
3  | Tool    :                  systemcass                       |
4  |                                                             |
5  | File    :                 gen_code.cc                       |
6  |                                                             |
7  | Author  :                 Taktak Sami                       |
8  |                           Buchmann Richard                  |
9  |                                                             |
10  | Date    :                   09_07_2004                      |
11  |                                                             |
12  \------------------------------------------------------------*/
13
14/*
15 * This file is part of the Disydent Project
16 * Copyright (C) Laboratoire LIP6 - Département ASIM
17 * Universite Pierre et Marie Curie
18 *
19 * Home page          : http://www-asim.lip6.fr/disydent
20 * E-mail             : mailto:richard.buchmann@lip6.fr
21 *
22 * This library is free software; you  can redistribute it and/or modify it
23 * under the terms  of the GNU Library General Public  License as published
24 * by the Free Software Foundation; either version 2 of the License, or (at
25 * your option) any later version.
26 *
27 * Disydent is distributed  in the hope  that it  will be
28 * useful, but WITHOUT  ANY WARRANTY; without even the  implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
30 * Public License for more details.
31 *
32 * You should have received a copy  of the GNU General Public License along
33 * with the GNU C Library; see the  file COPYING. If not, write to the Free
34 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
35 */
36
37#if defined(__linux__)
38#include <linux/limits.h>
39#elif defined(WIN32)
40#include <windows.h>
41#elif defined(__MACH__)
42#include <sys/syslimits.h>
43#endif
44
45#include <unistd.h>
46#include <cstring>
47#include <cstdio>
48#include <cstdlib>
49#include <iostream>
50#include <fstream>
51#ifdef USE_OPENMP
52    #include <omp.h>
53#endif
54
55#include "internal.h"
56#include "gen_code.h"
57#include "sc_module.h"
58#include "sc_ver.h"
59#include "process_dependency.h"
60
61#ifdef HAVE_CONFIG_H
62#include "config.h"
63#endif
64
65#define casc_cflags GENERATED_MODULE_CFLAGS
66
67// Enable CPP call, this is useful for typeinfo-enabled classes
68#define CPP_CALL
69
70using namespace std;
71
72namespace sc_core {
73
74static void PrintCall(std::ostream &, const method_process_t &);
75static void open_temp(std::ofstream &, char *);
76typedef void (*CASC_ENTRY_FUNC) (void *);
77typedef union {
78    unsigned long long int integer;
79    SC_ENTRY_FUNC pmf;
80    CASC_ENTRY_FUNC pf;
81} fct;
82
83
84const char * get_pmf_type() {
85    switch (sizeof(SC_ENTRY_FUNC)) {
86        case 4:
87            // G4 pointer-to-member-function style
88            return "unsigned long int";
89        case 8:
90            // PC pointer-to-member-function style
91            return "unsigned long long int";
92        default:
93            cerr <<
94                "Internal Error : Unsupported pointer-to-member-function"
95                "(size: " << sizeof(SC_ENTRY_FUNC) << ").\n"
96                "Please try --nodynamiclink option.\n";
97            exit(21072009);
98    };
99}
100
101
102static ostream & operator <<(ostream & o, const SC_ENTRY_FUNC & f) {
103    register fct p;
104    p.integer = 0;
105    p.pmf = f;
106    return o << "0x" << hex << p.integer << "ULL";
107}
108
109
110static void PrintCall(std::ostream & o, const method_process_t & m) {
111    SC_ENTRY_FUNC func = m.func;
112    if (print_schedule) {
113        o << "    fprintf(stderr,\"evaluation de " << m.module->name() << "->" << m.name << "()\\n\");\n";
114    }
115    o << " p.integer = " << func << ";\n";
116#ifdef CPP_CALL
117    o << " (((sc_module*)(" << m.module << "))->*(p.pmf)) (); /* " << m.module->name() << "->" << m.name << "() */\n";
118#else
119    o << " p.pf((void *)" << m.module << "); /* " << m.module->name() << "->" << m.name << "() */\n";
120#endif
121}
122
123
124static bool is_exist(const char * temp) {
125    ifstream o;
126    o.open(temp, ios::in);
127    if (o.is_open() == false) {
128        return false;
129    }
130    if (o.peek() == -1) {
131        return false;
132    }
133    return true;
134}
135
136
137static void open_temp(ofstream & o, char * temp) {
138    /*
139       srand (time (NULL));
140       int r = rand () % 1000;
141       */
142    pid_t pid = getpid();
143    int r = -1;
144    do {
145        sprintf(temp, "%s/scheduling-%d-%x.cc", temporary_dir, pid, ++r);
146    } while (is_exist(temp));
147
148    o.open(temp, ios::out);
149    if (o.is_open() == false) {
150        cerr << "Error : Unable to open a file to write scheduling code.\n";
151        exit(30032005);
152    }
153#ifdef CONFIG_DEBUG
154    cerr << "opened temporary filename : " << temp << "\n";
155#endif
156    sprintf(temp, "scheduling-%d-%x", pid, r++);
157}
158
159
160static char * gen_transition(ofstream & o, method_process_list_t & transition_func_list) {
161    // transitions
162    o << "\ninline void transition(void)\n{\n";
163    if (transition_func_list.empty() == false) {
164        o << " /* fonctions de transition */\n" << " register fct p;\n";
165        method_process_list_t::iterator mm;
166        for (mm = transition_func_list.begin(); mm != transition_func_list.end(); ++mm) {
167            PrintCall(o, **mm);
168        }
169    }
170    o << "}\n";
171}
172
173
174static char * gen_moore(ofstream & o, method_process_list_t & moore_func_list) {
175    // Moore generations (sequential functions)
176    o << "\ninline void moore_generation (void)\n{\n";
177    if (moore_func_list.empty() == false) {
178        o << "  /* fonctions de generation de Moore */\n"
179            << " register fct p;\n";
180        method_process_list_t::reverse_iterator mm;
181        for (mm = moore_func_list.rbegin();
182                mm != moore_func_list.rend(); ++mm) {
183            PrintCall(o, **mm);
184        }
185    }
186    o << " \n}\n";
187}
188
189
190static char * gen_mealy(ofstream & o, strong_component_list_t & strongcomponents) {
191    // Mealy generations (combinational functions only)
192    o << "\nextern void mealy_generation (void)\n{\n";
193    if (strongcomponents.empty()) {
194        return NULL;
195    }
196    o << "  register fct p;\n" << "\n\n  /* fonctions de mealy */\n";
197#ifdef NO_STATIC_SCHEDULE
198    o << "\n  do {\n    unstable = 0;\n";
199#endif
200    strong_component_list_t::iterator ss;
201    for (ss = strongcomponents.begin(); ss != strongcomponents.end(); ++ss) {
202        if ((*ss)->size() == 1) {
203            /* un seul element dans le strong component */
204            method_process_t *m = (method_process_t *) (*((*ss)->begin()));
205            PrintCall(o, *m);
206            continue;
207        }
208        else {
209            /* plusieurs elements dans le strong component */
210#ifndef NO_STATIC_SCHEDULE
211            o << "\n  do {\n    unstable = 0;\n";
212#endif
213            component_list_t::reverse_iterator rev_mm;
214            for (rev_mm = (*ss)->rbegin(); rev_mm != (*ss)->rend(); ++rev_mm) {
215                method_process_t * m = (method_process_t *) * rev_mm;
216                PrintCall(o, *m);
217            }
218#ifndef NO_STATIC_SCHEDULE
219            o << "  } while ( unstable );\n\n";
220#endif
221        }
222    }
223#ifdef NO_STATIC_SCHEDULE
224    o << "  } while ( unstable );\n\n";
225#else
226    o << "\tunstable = 0;\n";
227#endif
228}
229
230
231static char * gen_mealy(ofstream & o, ProcessDependencyList & mealy_func_list) {
232    // Mealy generations (combinational functions only)
233    o << "\nextern void mealy_generation (void)\n{\n";
234    o << "  register fct p;\n" << "\n\n  /* fonctions de mealy */\n";
235    ProcessDependencyList::iterator it;
236    for (it = mealy_func_list.begin(); it != mealy_func_list.end(); ++it) {
237        const method_process_t * m = *it;
238        PrintCall(o, *m);
239    }
240}
241
242
243char * gen_scheduling_code_for_dynamic_link(method_process_list_t & transition_func_list,
244        method_process_list_t & moore_func_list,
245        strong_component_list_t * strongcomponents) {
246    if (dump_stage) {
247        cerr << "Generating C code for scheduling...\n";
248    }
249
250    // open temporary file
251    ofstream o;
252    char base_name[PATH_MAX];
253    open_temp(o, base_name);
254
255    if (!o.good()) {
256        perror("scheduling: open file\n");
257        exit(-1);
258    }
259
260    o << "// generated by " << sc_version() << endl
261        << "#include <casc.h>\n\n" << "#include <cstdio>\n\n"
262        //  << "#include <iostream>\n\n"
263        << "namespace sc_core {\n"
264        << " typedef void (sc_module::*SC_ENTRY_FUNC)();\n"
265        << " typedef void (*CASC_ENTRY_FUNC)(void *);\n";
266
267    const char * pmf_type = get_pmf_type();
268
269    o << " typedef union { "
270        << pmf_type
271        << " integer; SC_ENTRY_FUNC pmf; CASC_ENTRY_FUNC pf; } fct;\n";
272
273    gen_transition(o, transition_func_list);
274    gen_moore(o, moore_func_list);
275    if (strongcomponents != NULL) {
276        gen_mealy      (o, *strongcomponents);
277    }
278
279    o << " \n}\n";
280    o << "\n} // end of sc_core namespace\n";
281
282    o.flush();
283    o.close();
284
285    // add "cc" extension
286    char file_name[PATH_MAX];
287    strncpy(file_name, base_name, PATH_MAX);
288    file_name[strlen(base_name)] = '\0';
289    strcat(file_name, ".cc");
290    rename(base_name, file_name);
291
292    if (edit_schedule) {
293        run_schedule_editor(file_name);
294    }
295
296    if (dump_stage) {
297        cerr << "Generating C code for scheduling done.\n";
298    }
299
300    return strdup(base_name);
301}
302
303
304char * gen_scheduling_code_for_dynamic_link(method_process_list_t & transition_func_list,
305        method_process_list_t & moore_func_list,
306        ProcessDependencyList & mealy_func_list) {
307    if (dump_stage) {
308        cerr << "Generating C code for scheduling...\n";
309    }
310
311    // open temporary file
312    ofstream o;
313    char base_name[PATH_MAX];
314    open_temp(o, base_name);
315
316    if (!o.good()) {
317        perror("scheduling: open file\n");
318        exit(-1);
319    }
320
321    o << "// generated by " << sc_version() << endl
322        << "#include <casc.h>\n\n" << "#include <cstdio>\n\n"
323        << "namespace sc_core {\n"
324        << " typedef void (sc_module::*SC_ENTRY_FUNC)();\n"
325        << " typedef void (*CASC_ENTRY_FUNC)(void *);\n"
326        <<
327        " typedef union { unsigned long long int integer; SC_ENTRY_FUNC pmf; CASC_ENTRY_FUNC pf; } fct;\n";
328
329    gen_transition(o, transition_func_list);
330    gen_moore(o, moore_func_list);
331    gen_mealy(o, mealy_func_list);
332
333    o << "\n}\n";
334    o << "\n} // end of sc_core namespace\n";
335
336    o.flush();
337    o.close();
338
339    // add "cc" extension
340    char file_name[PATH_MAX];
341    strncpy(file_name, base_name, PATH_MAX);
342    file_name[strlen(base_name)] = '\0';
343    strcat(file_name, ".cc");
344    rename(base_name, file_name);
345
346    if (edit_schedule) {
347        run_schedule_editor(file_name);
348    }
349
350    if (dump_stage) {
351        cerr << "Generating C code for scheduling done.\n";
352    }
353
354    return strdup(base_name);
355}
356
357
358/* base_name est la base du nom du fichier C++
359 * casc_cflags est une string qui correspond à $(INCLUDE) d'un Makefile
360 */
361void compile_code(const char * base_name, const char * casc_cflags2) {
362    if (dump_stage) {
363        cerr << "Compiling C/C++ code for scheduling...\n";
364    }
365    char compil_str[512];
366    const char * compiler = getenv("CXX");
367    const char * systemc_dir = getenv("SYSTEMCASS");
368    const char * default_compiler =
369#ifdef CPP_CALL
370        "g++";
371#else
372    "gcc";
373#endif
374
375    compiler = (compiler == NULL) ? default_compiler : compiler;
376    if (systemc_dir == NULL) {
377        systemc_dir = getenv("SYSTEMC");
378        if (systemc_dir == NULL) {
379            cerr << "Error : set SYSTEMCASS or SYSTEMC environnement variable to the SYSTEMCASS directory.\n";
380            exit(-1);
381        }
382    }
383
384    char target_name[128];
385    char source_name[128];
386    sprintf(target_name, "%s.lo", base_name);
387    sprintf(source_name, "%s.cc", base_name);
388
389    if (keep_generated_code) {
390        char lg_cde[256];
391        sprintf(lg_cde, "mkdir -p %s", generated_files_dir);
392        system(lg_cde);
393        sprintf(lg_cde, "cp %s/%s %s/", temporary_dir, source_name, generated_files_dir);
394        if (dump_stage) {
395            cerr << "$ " << lg_cde << "\n";
396        }
397        system(lg_cde);
398        sprintf(lg_cde, "(cd %s ; indent %s)", generated_files_dir, source_name);
399        if (dump_stage) {
400            cerr << "$ " << lg_cde << "\n";
401        }
402        system(lg_cde);
403    }
404    /* ******* */
405    /* COMPILE */
406    /* ******* */
407    const char * commandline_template =
408#if defined(CONFIG_OS_DARWIN)
409        "(cd %s ;"
410        " %s %s -DSCHEDULING_BY_CASC -I%s/include -fno-common -dynamic -o %s -c %s)"
411#elif defined(CONFIG_OS_LINUX)
412        "(cd %s ; libtool --mode=compile %s %s -DSCHEDULING_BY_CASC -I%s/include -shared -o %s -c %s)"
413#else
414        "(cd %s ;"
415        " %s %s -DSCHEDULING_BY_CASC -I%s/include -dynamiclib -o %s -c %s)"
416#endif
417        ;
418
419    string cflags = casc_cflags;
420    if (use_openmp) {
421        cflags += " -fopenmp";
422    }
423
424    sprintf(compil_str, commandline_template, temporary_dir, compiler, cflags.c_str(), systemc_dir, target_name, source_name);
425
426    if (dump_stage) {
427        cerr << "Executing command : " << compil_str << "\n";
428    }
429
430    if (system(compil_str)) {
431        perror("compil : system");
432        exit(-1);
433    }
434
435    /* **** */
436    /* LINK */
437    /* **** */
438    sprintf(target_name, "%s.la", base_name);
439
440#ifdef CONFIG_OS_LINUX
441    sprintf(source_name, "%s.lo", base_name);
442    sprintf(compil_str, "(cd %s ; pwd ; libtool --mode=link %s %s -module -shared -o %s %s -rpath /tmp)", /* -L. -L%s/lib-%s */
443            temporary_dir, compiler, casc_cflags, /*systemc_dir, target_arch, */
444            target_name, source_name);
445#else
446    sprintf(source_name, "%s.o", base_name);
447    sprintf(compil_str, "(cd %s ; pwd ; libtool -dynamic -o %s %s)", temporary_dir, target_name, source_name);
448#endif
449
450    if (dump_stage) {
451        cerr << "Executing command : " << compil_str << "\n";
452    }
453
454    if (system(compil_str)) {
455        perror("compil : system");
456        exit(-1);
457    }
458
459    system(compil_str);
460    if (dump_stage) {
461        cerr << "Compiling done.\n";
462    }
463}
464
465
466/* ********************************
467 * Function for static scheduling
468 */
469struct function_call {
470    fct * function;
471    void ** instance;
472    int func_number;
473};
474static function_call pf[3];
475
476
477void get_function_call(function_call & pf, method_process_list_t & func_list) {
478    pf.func_number = func_list.size();
479    pf.function = (fct *) malloc(sizeof(fct) * pf.func_number);
480    pf.instance = (void **) malloc(sizeof(void *) * pf.func_number);
481    method_process_list_t::iterator mm;
482    int i;
483    for (mm = func_list.begin(), i = 0; mm != func_list.end(); ++mm, ++i) {
484        const method_process_t * mp = *mm;
485        pf.function[i].pmf = (mp->func);
486        pf.instance[i] = (void *) (mp->module);
487    }
488}
489
490
491void get_function_call(function_call & pf, ProcessDependencyList & func_list) {
492    pf.func_number = func_list.size();
493    pf.function = (fct *) malloc(sizeof(fct) * pf.func_number);
494    pf.instance = (void **) malloc(sizeof(void *) * pf.func_number);
495    ProcessDependencyList::iterator it;
496    int i;
497    for (i = 0, it = func_list.begin(); it != func_list.end(); ++it, ++i) {
498        const method_process_t * mp = *it;
499        pf.function[i].pmf = (mp->func);
500        pf.instance[i] = (void *) (mp->module);
501    }
502}
503
504
505void gen_scheduling_code_for_static_func(method_process_list_t & transition_func_list,
506        method_process_list_t & moore_func_list,
507        ProcessDependencyList & mealy_func_list) {
508    if (dump_stage) {
509        cerr << "Generating scheduling...\n";
510    }
511
512    get_function_call(pf[0], transition_func_list);
513    get_function_call(pf[1], moore_func_list);
514    get_function_call(pf[2], mealy_func_list);
515
516    if (dump_stage) {
517        cerr << "Generating scheduling done.\n";
518    }
519}
520
521
522void call_functions(function_call & fc) {
523    int n = fc.func_number;
524    int i;
525    for (i = 0; i < n; ++i) {
526#if 0
527        //defined(CONFIG_DEBUG)
528        sc_module *m = (sc_module *) (fc.instance[i]);
529        cerr << m->name() << endl;
530#endif
531        fc.function[i].pf(fc.instance[i]);
532    }
533}
534
535
536void call_functions_in_parallel(function_call & fc) {
537    int n = fc.func_number;
538    int i;
539    for (i = 0; i < n; ++i) {
540#if 0
541        //defined(CONFIG_DEBUG)
542        sc_module *m = (sc_module *) (fc.instance[i]);
543        cerr << m->name() << endl;
544        cerr << "thread #" << omp_get_thread_num() << endl;
545#endif
546        fc.function[i].pf(fc.instance[i]);
547    }
548}
549
550
551void static_mealy_generation() {
552    call_functions(pf[2]);
553}
554
555
556void static_simulate_1_cycle(void) {
557    call_functions(pf[0]); // transition
558    update();
559    call_functions_in_parallel(pf[1]); // moore generation
560    call_functions(pf[2]); // mealy generation
561}
562
563
564/* ***************************************
565 * Function for quasi static scheduling
566 */
567
568unsigned int nb_func[2];
569static method_process_t **func_list[2];
570static strong_component_list_t quasistatic_list;
571
572unsigned long long busy_wait_f0, busy_wait_f1, busy_wait_up, busy_wait_ml;
573unsigned long long last_wait_f0, last_wait_f1, last_wait_up, last_wait_ml;
574#ifdef USE_OPENMP
575#pragma omp threadprivate (nb_func, func_list)
576#pragma omp threadprivate (busy_wait_f0, busy_wait_f1, busy_wait_up,busy_wait_ml)
577#pragma omp threadprivate (last_wait_f0, last_wait_f1, last_wait_up,last_wait_ml)
578#endif
579
580static void Call(const method_process_t & m) {
581    sc_module * mod = m.module;
582    SC_ENTRY_FUNC func = m.func;
583    //  CASC_ENTRY_FUNC   func = reinterpret_cast<CASC_ENTRY_FUNC> (m.func);
584    // QM
585    //cerr << "Exec " << mod->name() << "->" << m.name << endl;
586    (mod->*func) ();
587}
588
589
590void quasistatic_mealy_generation() {
591    strong_component_list_t::iterator ss;
592    for (ss = quasistatic_list.begin(); ss != quasistatic_list.end(); ++ss) {
593        if ((*ss)->size() == 1) {
594            /* un seul element dans le strong component */
595            method_process_t * m = (method_process_t *) (*((*ss)->begin()));
596            Call(*m);
597            continue;
598        }
599        else {
600            /* plusieurs elements dans le strong component */
601            do {
602                unstable = 0;
603                component_list_t::reverse_iterator rev_mm;
604                for (rev_mm = (*ss)->rbegin(); rev_mm != (*ss)->rend(); ++rev_mm) {
605                    method_process_t * m = (method_process_t *) * rev_mm;
606                    Call(*m);
607                }
608            } while (unstable);
609        }
610    }
611}
612
613unsigned int expected_globaltime = 0;
614volatile unsigned int globaltime __attribute__ ((aligned (128))) = 0;
615#ifdef USE_OPENMP
616#pragma omp threadprivate (expected_globaltime)
617#pragma omp shared (globaltime)
618#endif
619
620unsigned int num_omp_threads;
621
622void quasistatic_simulate_1_cycle(void) {
623    int i;
624
625    for (i = 0; i < nb_func[0]; ++i) {
626        Call(*(func_list[0][i]));
627    }
628#define USE_BUSY_WAIT 1
629    update();
630#if USE_BUSY_WAIT
631    expected_globaltime += num_omp_threads;
632    if (__sync_add_and_fetch(&globaltime, 1) == expected_globaltime) {
633        last_wait_up++;
634    }
635    __asm volatile("mfence");
636    while (globaltime < expected_globaltime) {
637        busy_wait_up++;
638        __asm volatile("lfence");
639    }
640#else
641    #ifdef USE_OPENMP
642    #pragma omp barrier
643    #endif
644#endif
645
646    for (i = 0; i < nb_func[1]; ++i) {
647        Call(*(func_list[1][i]));
648    }
649
650#if USE_BUSY_WAIT
651    expected_globaltime += num_omp_threads;
652    if (__sync_add_and_fetch(&globaltime, 1) == expected_globaltime) {
653        last_wait_f1++;
654    }
655    __asm volatile("mfence");
656    while (globaltime < expected_globaltime) {
657        busy_wait_f1++;
658        __asm volatile("lfence");
659    }
660#else
661    #ifdef USE_OPENMP
662    #pragma omp barrier
663    #endif
664#endif
665    if (!quasistatic_list.empty()) {
666#ifdef USE_OPENMP
667#pragma omp master
668#endif
669        {
670            quasistatic_mealy_generation();
671        }
672#if USE_BUSY_WAIT
673        expected_globaltime += num_omp_threads;
674        if (__sync_add_and_fetch(&globaltime, 1) == expected_globaltime) {
675            last_wait_ml++;
676        }
677        __asm volatile("mfence");
678        while (globaltime < expected_globaltime) {
679            busy_wait_ml++;
680            __asm volatile("lfence");
681        }
682#else
683    #ifdef USE_OPENMP
684    #pragma omp barrier
685    #endif
686#endif
687    }
688}
689
690
691void gen_scheduling_code_for_quasistatic_func(method_process_list_t & transition_func_list,
692        method_process_list_t & moore_func_list,
693        strong_component_list_t * mealy_func_list) {
694    if (dump_stage) {
695        cerr << "Generating quasi static scheduling...\n";
696    }
697
698    nb_func[0] = transition_func_list.size();
699    nb_func[1] = moore_func_list.size();
700
701    func_list[0] = (method_process_t**) malloc(sizeof (method_process_t*) * nb_func[0]);
702    func_list[1] = (method_process_t**) malloc(sizeof (method_process_t*) * nb_func[1]);
703
704    unsigned int i;
705    for (i = 0; i < nb_func[0]; ++i) {
706        func_list[0][i] = (transition_func_list[i]);
707    }
708
709    for (i = 0; i < nb_func[1]; ++i) {
710        func_list[1][i] = (moore_func_list[i]);
711    }
712
713    if (mealy_func_list != NULL) {
714        quasistatic_list = *mealy_func_list;
715    }
716
717    if (dump_stage) {
718        cerr << "Generating quasi static scheduling done.\n";
719    }
720}
721} // end of sc_core namespace
722
723
724/*
725# Local Variables:
726# tab-width: 4;
727# c-basic-offset: 4;
728# c-file-offsets:((innamespace . 0)(inline-open . 0));
729# indent-tabs-mode: nil;
730# End:
731#
732# vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4
733*/
Note: See TracBrowser for help on using the repository browser.