root/trunk/thune/gc.c

Revision 557, 19.3 kB (checked in by krobillard, 7 weeks ago)

Fixed float/int conversion with -O3.

Line 
1/*============================================================================
2    Thune Interpreter
3    Copyright (C) 2005-2006  Karl Robillard
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18===========================================================================*/
19
20
21#include "os.h"
22#include "urlan.h"
23#include "internal.h"
24//#include "pairpool.h"
25
26
27#ifdef DEBUG
28//#define GC_REPORT   1
29//#define GC_VERBOSE  1
30#endif
31
32//#define GC_TIME     1
33#ifdef GC_TIME
34#include <time.h>
35#endif
36
37
38static void _freeArrayX( UArray* arr, UGCArray* agc, UArray* abuf )
39{
40    if( arr->ptr.v )
41    {
42        memFree( arr->ptr.v );
43        arr->ptr.v  = 0;
44    }
45
46    // Flag as unused.
47    arr->avail = -1;
48
49    // Link to free list.
50    arr->used  = agc->freeList;
51    agc->freeList = arr - abuf;
52
53    agc->freeCount += 1;
54}
55
56
57#if 0
58/**
59  Do not call ur_freeBlock() from inside garbage collector.
60*/
61void ur_freeBlock( UIndex blkN )
62{
63    UArray* abuf = (UArray*) ut->blocks.arr.ptr.v;
64    _freeArrayX( abuf + blkN, &ut->blocks, abuf );
65}
66#endif
67
68
69#define FREE_ARRAY(it) \
70    tmp = it; \
71    if( tmp->avail > -1 ) \
72        _freeArrayX( tmp, af, abuf );
73
74static void _sweepArray( UArray* mark, UGCArray* af )
75{
76    UArray* tmp;
77    UArray* arr  = &af->arr;
78    UArray* abuf = (UArray*) arr->ptr.v;
79    UArray* ait  = abuf;
80    uint8_t* it  = mark->ptr.b;
81    uint8_t* end = it + mark->used;
82    int count = arr->used;
83
84
85    // Mark padding bits at end as used.
86    if( count & 7 )
87        end[-1] |= 0xff << (count & 7);
88
89
90    // Check mark bitset for unused elements.
91    while( it != end )
92    {
93        if( *it != 0xff )
94        {
95            int mask = *it;
96
97#if 0
98            dprint( "_sweepArray %p elem: %d 0x%02x\n",
99                    arr->buf, ait - ((UArray*) arr->buf), mask );
100#endif
101
102            if( (mask & 0x0f) != 0x0f )
103            {
104                if( (mask & 0x01) == 0 ) { FREE_ARRAY( ait ) }
105                if( (mask & 0x02) == 0 ) { FREE_ARRAY( ait + 1 ) }
106                if( (mask & 0x04) == 0 ) { FREE_ARRAY( ait + 2 ) }
107                if( (mask & 0x08) == 0 ) { FREE_ARRAY( ait + 3 ) }
108            }
109
110            if( (mask & 0xf0) != 0xf0 )
111            {
112                if( (mask & 0x10) == 0 ) { FREE_ARRAY( ait + 4 ) }
113                if( (mask & 0x20) == 0 ) { FREE_ARRAY( ait + 5 ) }
114                if( (mask & 0x40) == 0 ) { FREE_ARRAY( ait + 6 ) }
115                if( (mask & 0x80) == 0 ) { FREE_ARRAY( ait + 7 ) }
116            }
117        }
118        ++it;
119        ait += 8;
120    }
121
122
123#if 0
124    // Move the top of the array down.
125    ait = ((UArray*) arr->buf) + arr->used - 1;
126    while( arr->used && (ait->avail == -1) )
127    {
128        --ait;
129        --arr->used;
130        *_freeCount -= 1;
131    }
132#endif
133}
134
135
136static void _freeResource( UDatatype* customDT, UGCArray* agc, UResource* buf )
137{
138    int idx = ((int) buf->dataType) - UT_BI_COUNT;
139    if( idx >= 0 )
140    {
141#if 0
142        printf( "freeResource %ld %s\n",
143                buf - (UResource*) agc->arr.ptr.v, customDT[idx].name );
144#endif
145        customDT[ idx ].gcDestroyResource( buf );
146    }
147
148    // Flag as unused.
149    buf->dataType = UT_UNSET;
150
151    // Link to free list.
152    buf->link = agc->freeList;
153    agc->freeList = buf - ((UResource*) agc->arr.ptr.v);
154
155    agc->freeCount += 1;
156}
157
158
159#define FREE_BUFFER(buf) \
160    btmp = buf; \
161    if( btmp->dataType != UT_UNSET ) \
162        _freeResource( dt, agc, btmp );
163
164static void _sweepResources( UDatatype* dt, UArray* mark, UGCArray* agc )
165{
166    UResource* btmp;
167    UResource* ait = (UResource*) agc->arr.ptr.v;
168    uint8_t* it  = mark->ptr.b;
169    uint8_t* end = it + mark->used;
170
171
172    // Mark padding bits at end as used.
173    {
174    int padBits = agc->arr.used & 7;
175    if( padBits )
176        end[-1] |= 0xff << padBits;
177    }
178
179    // Check mark bitset for unused elements.
180    while( it != end )
181    {
182        if( *it != 0xff )
183        {
184            int mask = *it;
185
186            if( (mask & 0x0f) != 0x0f )
187            {
188                if( (mask & 0x01) == 0 ) { FREE_BUFFER( ait ) }
189                if( (mask & 0x02) == 0 ) { FREE_BUFFER( ait + 1 ) }
190                if( (mask & 0x04) == 0 ) { FREE_BUFFER( ait + 2 ) }
191                if( (mask & 0x08) == 0 ) { FREE_BUFFER( ait + 3 ) }
192            }
193
194            if( (mask & 0xf0) != 0xf0 )
195            {
196                if( (mask & 0x10) == 0 ) { FREE_BUFFER( ait + 4 ) }
197                if( (mask & 0x20) == 0 ) { FREE_BUFFER( ait + 5 ) }
198                if( (mask & 0x40) == 0 ) { FREE_BUFFER( ait + 6 ) }
199                if( (mask & 0x80) == 0 ) { FREE_BUFFER( ait + 7 ) }
200            }
201        }
202        ++it;
203        ait += 8;
204    }
205}
206
207
208void ur_gcMarkBin( UCollector* gc, UIndex idx )
209{
210    ur_setBit( gc->bsBin.ptr.b, idx );
211}
212
213
214void ur_gcMarkBlock( UCollector* gc, UIndex idx )
215{
216    ur_setBit( gc->bsBlock.ptr.b, idx );
217}
218
219
220void ur_gcMarkResource( UCollector* gc, UIndex idx )
221{
222    ur_setBit( gc->bsRes.ptr.b, idx );
223}
224
225
226#define SET_BIT_BLOCK(bn) \
227    ur_setBit( gc->bsBlock.ptr.b, bn );
228
229#define SET_BIT_BLOCK_L(bn) \
230    idx = bn; if( ur_isLocal(idx) ) ur_setBit( gc->bsBlock.ptr.b, idx );
231
232#define SET_BIT_BIN(bn) \
233    ur_setBit( gc->bsBin.ptr.b, bn );
234
235#define SET_BIT_BIN_L(bn) \
236    idx = bn; if( ur_isLocal(idx) ) ur_setBit( gc->bsBin.ptr.b, idx );
237
238
239static void _checkBlock( UThread* ut, UCollector* gc, UBlock* blk )
240{
241    UCell* it  = blk->ptr.cells;
242
243#ifdef GC_VERBOSE
244    printf( "GC _checkBlock %ld\n", orBlockN(blk) );
245#endif
246
247    if( it )
248        ur_gcMarkCells( ut, gc, it, it + blk->used );
249}
250
251
252void ur_gcMarkCells( UThread* ut, UCollector* gc, UCell* it, const UCell* end )
253{
254    int idx;
255    while( it != end )
256    {
257        switch( ur_type(it) )
258        {
259            case UT_UNSET:
260            case UT_DATATYPE:
261            case UT_NONE:
262            case UT_LOGIC:
263            case UT_OPCODE:
264            case UT_CHAR:
265            case UT_INT:
266            case UT_DECIMAL:
267            case UT_COORD:
268            case UT_VEC3:
269                break;
270
271            case UT_WORD:
272            case UT_SETWORD:
273            //case UT_LITWORD:      // Only atom is used in lit-words?
274                SET_BIT_BLOCK_L( it->word.wordBlk )
275                idx = it->word.valBlk;
276                if( idx > 0 )
277                    SET_BIT_BLOCK( idx )
278                break;
279
280            case UT_BINARY:
281            case UT_STRING:
282            case UT_VECTOR:
283            case UT_BITSET:
284                SET_BIT_BIN_L( it->series.n )
285                break;
286
287            case UT_CONTEXT:
288            case UT_PORT:
289            case UT_COMPONENT:
290                idx = it->ctx.wordBlk;
291                if( ur_isLocal(idx) )
292                {
293                    // Word block only contains words so block is marked
294                    // as checked.
295                    int ai   = idx >> 3;
296                    int bitm = 1 << (idx & 7);
297                    gc->bsBlock.ptr.b[ ai ] |= bitm;
298                    gc->blkChecked.ptr.b[ ai ] |= bitm;
299                }
300
301                SET_BIT_BLOCK_L( it->ctx.valBlk )
302                break;
303
304            case UT_FUNCTION:
305                SET_BIT_BLOCK_L( it->func.bodyN )
306                SET_BIT_BLOCK_L( it->func.sigN )
307                //SET_BIT_BLOCK_L( it->func.closureN )
308                break;
309
310            case UT_LIST:
311            case UT_BLOCK:
312            case UT_PAREN:
313            case UT_MACRO:
314#ifdef UR_CONFIG_HASHMAP
315mark_block:
316#endif
317                idx = it->series.n;
318                if( ur_isLocal(idx) )
319                {
320                    int ai   = idx >> 3;
321                    int bitm = 1 << (idx & 7);
322                    gc->bsBlock.ptr.b[ ai ] |= bitm;
323                    if( ! (gc->blkChecked.ptr.b[ ai ] & bitm) )
324                    {
325                        gc->blkChecked.ptr.b[ ai ] |= bitm;
326                        _checkBlock( ut, gc, ur_block( it ) );
327                    }
328                }
329                break;
330
331#ifdef PAIRPOOL_H
332            case UT_SLIST:
333            {
334                UCell* pair = uc_pair(it);
335                ur_pairPoolGCMark( it );
336            }
337                break;
338#endif
339            case UT_PATH:
340            case UT_SETPATH:
341            //case UT_LITPATH:
342                SET_BIT_BLOCK_L( it->series.n )
343                break;
344
345            case UT_ERROR:
346                idx = it->err.messageStr;
347                if( idx )
348                    SET_BIT_BIN( idx )
349
350                idx = it->err.traceBlk;
351                if( idx > 0 )
352                    SET_BIT_BLOCK( idx )
353                break;
354
355#ifdef UR_CONFIG_DT_CODE
356            case UT_CODE:
357            {
358                ur_language( it )->codeGC( gc, it );
359            }
360                break;
361#endif
362#ifdef UR_CONFIG_HASHMAP
363            case UT_HASH_MAP:
364                SET_BIT_BIN_L( ur_hashMapBin(it) )
365                goto mark_block;
366#endif
367            default:
368                idx = ur_type(it) - UT_BI_COUNT;
369                if( idx >= 0 )
370                    ut->env->customDT[ idx ].gcMark( gc, it );
371                break;
372        }
373        ++it;
374    }
375}
376
377
378#ifdef LANG_THUNE
379static void _checkControlStack( UThread* ut, UCollector* gc )
380{
381    int idx;
382    CEntry* it;
383    CEntry* end;
384
385    it  = UR_TOC - 1;
386    end = UR_BOC - 1;
387
388    while( it != end )
389    {
390        switch( it->id.code )
391        {
392            case CC_FUNC:
393            case CC_FUNC_LOOP:
394                SET_BIT_BLOCK_L( it->func.bodyN )
395                idx = it->func.sigN;
396                if( idx > 0 )
397                    SET_BIT_BLOCK( idx )
398                it -= CC_LEN_FUNC;
399                break;
400
401            case CC_EVAL:
402            case CC_EVAL_RUNNING:
403            case CC_FOREVER:
404                SET_BIT_BLOCK_L( it->eval.n )
405                it -= CC_LEN_EVAL;
406                break;
407
408            case CC_LOOP:
409                --it;
410                SET_BIT_BLOCK_L( it->cell.series.n )
411                --it;
412                break;
413
414            case CC_REDUCE:
415                SET_BIT_BLOCK_L( it->reduce.n )
416                it -= CC_LEN_REDUCE;
417                break;
418
419            case CC_ITER:
420            case CC_EACH:
421            {
422                UCell* cell = ((UCell*) it) - 1;
423                SET_BIT_BLOCK_L( it->cp.n )
424                ur_gcMarkCells( ut, gc, cell, cell + 1 );
425                it -= CC_LEN_ITER;
426            }
427                break;
428
429            case CC_CATCH:
430                SET_BIT_BLOCK_L( (it - 1)->eval.n )
431                it -= CC_LEN_CATCH;
432                break;
433
434            case CC_TERM:
435            case CC_END:
436                SET_BIT_BLOCK_L( it->end.blkN )
437                it -= CC_LEN_END;
438                break;
439
440            default:
441                assert( 0 && "Invalid Control Code" );
442        }
443    }
444}
445#endif
446
447
448#ifdef LANG_RUNE
449static void _checkControlStack( UThread* ut, UCollector* gc )
450{
451    assert( UR_TOC >= UR_BOC );
452
453    ur_gcMarkCells( ut, gc, UR_BOC, UR_TOC );
454}
455#endif
456
457
458static void _checkDataStack( UThread* ut, UCollector* gc )
459{
460#ifndef LANG_RUNE
461    assert( ur_is(UR_BOS, UT_UNSET) );
462#endif
463    assert( UR_TOS >= UR_BOS );
464
465    ur_gcMarkCells( ut, gc, UR_BOS + 1, UR_TOS + 1 );
466}
467
468
469#if defined(GC_REPORT) || defined(DEBUG)
470typedef struct
471{
472    UGCArray  bin;
473    UGCArray  blocks;
474    UGCArray  resources;
475}
476DataStore;
477
478
479void ur_gcReport( DataStore* store )
480{
481    int used;
482    int unused;
483
484    dprint( "  Array    Buf       Max Used  Free    Bytes  Bytes Wasted\n" );
485    dprint( "  --------------------------------------------------------\n" );
486    {
487        UBinary* it  = (UBinary*) store->bin.arr.ptr.v;
488        UBinary* end = it + store->bin.arr.used;
489
490        used = 0;
491        unused = 0;
492        while( it != end )
493        {
494            if( it->avail > 0 )
495            {
496                used   += it->used;
497                unused += it->avail - it->used;
498            }
499            ++it;
500        }
501        dprint( "  Binaries %p   %4d   %4d   %7d   %7d\n",
502                store->bin.arr.ptr.v,
503                store->bin.arr.used,
504                store->bin.freeCount,
505                used, unused );
506    }
507    {
508        UBlock* it  = (UBlock*) store->blocks.arr.ptr.v;
509        UBlock* end = it + store->blocks.arr.used;
510
511        used = 0;
512        unused = 0;
513        while( it != end )
514        {
515            if( it->avail > 0 )
516            {
517                used   += it->used;
518                unused += it->avail - it->used;
519            }
520            ++it;
521        }
522        dprint( "  Blocks   %p   %4d   %4d   %7d   %7d\n",
523                store->blocks.arr.ptr.v,
524                store->blocks.arr.used,
525                store->blocks.freeCount,
526                used * 16, unused * 16 );
527    }
528    dprint( "\n" );
529}
530#endif
531
532
533/**
534  Perform garbage collection.
535
536  A tracing mark-sweep collector.
537  Starts with global context & data stack and only traces used values.
538*/
539void ur_recycle( UThread* ut )
540{
541    UCollector gc;
542    int byteSize;
543    uint8_t* markset;
544    uint8_t* doneset;
545    int i;
546    int bitm;
547    int ai;
548    UBlock* blk;
549    int blkCount;
550    UrlanEnv* env = ut->env;
551
552#ifdef GC_TIME
553    clock_t t1, t1e;
554#endif
555
556#define GC_BITSET(bin,ga,offset) \
557    byteSize = ga.arr.used - offset; \
558    byteSize = (byteSize + 7) / 8; \
559    ur_arrayInit(&bin, sizeof(uint8_t), byteSize); \
560    bin.used = byteSize; \
561    memSet(bin.ptr.v, 0, bin.avail)
562
563
564#ifdef GC_REPORT
565    int pass = 0;
566    dprint( "\nRecycle report:\n\n" );
567    ur_gcReport( (DataStore*) ut );
568#endif
569
570#ifdef GC_TIME
571    t1 = clock();
572#endif
573
574    GC_BITSET( gc.bsBin,   ut->bin,       0 );
575    GC_BITSET( gc.bsBlock, ut->blocks,    0 );
576    GC_BITSET( gc.bsRes,   ut->resources, 0 );
577
578    GC_BITSET( gc.blkChecked, ut->blocks, 0 );
579
580    gc.ut = ut;
581
582#ifdef PAIRPOOL_H
583    ur_pairPoolGCClear();
584#endif
585
586    if( env->dtCount > UT_BI_COUNT )
587    {
588        // Tell custom types marking will begin.
589        ai = 0;
590        for( i = UT_BI_COUNT; i < env->dtCount; ++i )
591        {
592            env->customDT[ ai ].recycle( env, UR_GC_PHASE_MARK );
593            ++ai;
594        }
595    }
596
597
598    // Mark all complex values used.
599    // Start by checking the data stack, global context and any blocks
600    // currently being referenced.
601
602    // Check global context.
603
604    blk = (UBlock*) ut->blocks.arr.ptr.v;
605
606    _checkBlock( ut, &gc, blk + BLK_THREAD_HOLD );
607    _checkBlock( ut, &gc, blk + BLK_GLOBAL_VAL );
608    _checkBlock( ut, &gc, blk + BLK_CTX_STACK );
609
610    doneset = gc.blkChecked.ptr.b;
611    ur_setBit( doneset, BLK_GLOBAL_WORD );
612    ur_setBit( doneset, BLK_GLOBAL_VAL );
613    ur_setBit( doneset, BLK_CTX_STACK );
614
615#if 1
616    gc.bsBlock.ptr.b[0] |= 0x0f;    // Quick version of code below.
617#else
618    ur_setBit( gc.bsBlock.ptr.b, BLK_THREAD_HOLD );
619    ur_setBit( gc.bsBlock.ptr.b, BLK_GLOBAL_WORD );
620    ur_setBit( gc.bsBlock.ptr.b, BLK_GLOBAL_VAL );
621    ur_setBit( gc.bsBlock.ptr.b, BLK_CTX_STACK );
622    //ur_setBit( gc.bsBlock.ptr.b, BLK_DSTACK );
623#endif
624
625    ur_setBit( gc.bsBin.ptr.b, BIN_THREAD_TMP );
626
627    _checkDataStack( ut, &gc );
628    _checkControlStack( ut, &gc );
629
630
631    // Blocks and objects may refer to other blocks and objects so we must
632    // check until no unchecked items are found.
633
634    do
635    {
636        blkCount = 0;
637
638        // Mark complex values used in blocks.
639        {
640        UBlock* bit;
641        UBlock* bend;
642
643        i = 0;
644        markset = gc.bsBlock.ptr.b;
645        doneset = gc.blkChecked.ptr.b;
646
647        bit  = blk;
648        bend = blk + ut->blocks.arr.used;
649
650        while( bit != bend )
651        {
652            ai   = i >> 3;
653            bitm = 1 << (i & 7);
654            if( markset[ ai ] & bitm )              // ur_bitIsSet(markset, i)
655            {
656                if( ! (doneset[ ai ] & bitm) )      // ur_bitIsSet(doneset, i)
657                {
658                    doneset[ ai ] |= bitm;          // ur_setBit(doneset, i)
659                    ++blkCount;
660                    _checkBlock( ut, &gc, bit );
661                }
662            }
663            ++bit;
664            ++i;
665        }
666        }
667
668#ifdef GC_REPORT
669        dprint( "  GC pass %d - blocks checked: %d\n", pass, blkCount );
670        ++pass;
671#endif
672    }
673    while( blkCount );
674
675
676    if( env->dtCount > UT_BI_COUNT )
677    {
678        // Tell custom types sweep will begin.
679        ai = 0;
680        for( i = UT_BI_COUNT; i < env->dtCount; ++i )
681        {
682            env->customDT[ ai ].recycle( env, UR_GC_PHASE_SWEEP );
683            ++ai;
684        }
685    }
686
687#ifdef PAIRPOOL_H
688    ur_pairPoolGCSweep();
689#endif
690
691    _sweepResources( env->customDT, &gc.bsRes, &ut->resources );
692    _sweepArray( &gc.bsBlock, &ut->blocks );
693    _sweepArray( &gc.bsBin,   &ut->bin );
694
695    ur_arrayFree( &gc.blkChecked );
696
697    ur_arrayFree( &gc.bsRes );
698    ur_arrayFree( &gc.bsBlock );
699    ur_arrayFree( &gc.bsBin );
700
701#ifdef GC_TIME
702    t1e = clock() - t1;
703    printf( "gc seconds: %g\n", ((double) t1e) / CLOCKS_PER_SEC );
704#endif
705
706#ifdef GC_REPORT
707    dprint( "  %d passes\n\n", pass );
708    ur_gcReport( (DataStore*) ut );
709#endif
710}
711
712
713/*--------------------------------------------------------------------------*/
714
715
716#ifdef TRACK_MALLOC
717typedef struct OListNode OListNode;
718
719struct OListNode
720{
721    OListNode*  prev;
722    OListNode*  next;
723};
724
725typedef struct
726{
727    OListNode   head;
728    OListNode   tail;
729}
730OList;
731
732static void _listAppend( OListNode* node, OListNode* after )
733{
734    node->prev = after;
735    node->next = after->next;
736    after->next->prev = node;
737    after->next = node;
738}
739
740static void _listRemove( OListNode* node )
741{
742    node->prev->next = node->next;
743    node->next->prev = node->prev;
744    node->prev = node->next = 0;
745}
746
747
748typedef struct
749{
750    OListNode link;
751    void* ptr;
752    size_t size;
753}
754MallocRecord;
755
756
757static OList _list = { {0,&_list.tail}, {&_list.head,0} };
758
759
760void* memAlloc( size_t size )
761{
762    MallocRecord* rec;
763    void* ptr = malloc( size );
764
765    if( ptr )
766    {
767        // Add ptr to list.
768
769        rec = (MallocRecord*) malloc( sizeof(MallocRecord) );
770        assert( rec );
771        rec->ptr  = ptr;
772        rec->size = size;
773        _listAppend( &rec->link, &_list.head );
774    }
775
776    return ptr;
777}
778
779
780void memFree( void* ptr )
781{
782    OListNode* it;
783    MallocRecord* rec;
784
785    // Remove ptr from list.
786
787    rec = 0;
788    it = _list.head.next;
789    while( it->next )
790    {
791        if( ((MallocRecord*) it)->ptr == ptr )
792        {
793            rec = (MallocRecord*) it;
794            break;
795        }
796        it = it->next;
797    }
798
799    if( rec )
800    {
801        free( rec->ptr );
802        _listRemove( &rec->link );
803        free( rec );
804    }
805    else
806    {
807        cprint( "memFree - %p was not allocted with memAlloc!\n", ptr );
808    }
809}
810
811
812void memReport( int verbose )
813{
814    size_t total;
815    int count;
816    OListNode* it;
817    MallocRecord* rec;
818
819    cprint( "memReport:\n" );
820
821    count = total = 0;
822    it = _list.head.next;
823    while( it->next )
824    {
825        rec = (MallocRecord*) it;
826
827        total += rec->size;
828        ++count;
829
830        if( verbose )
831            cprint( "  %d bytes at %p\n", rec->size, rec->ptr );
832
833        it = it->next;
834    }
835
836    cprint( "  %d allocations\n  %d bytes\n", count, total );
837}
838
839
840void orMemoryNative( UCell* a1 )
841{
842    (void) a1;
843    memReport( 0 );
844}
845#endif
846
847
848/*EOF*/
Note: See TracBrowser for help on using the browser.