OSDN Git Service

Initial revision
authorkseitz <kseitz>
Tue, 24 Sep 2002 19:56:08 +0000 (19:56 +0000)
committerkseitz <kseitz>
Tue, 24 Sep 2002 19:56:08 +0000 (19:56 +0000)
tcl/generic/tclThreadAlloc.c [new file with mode: 0644]
tcl/generic/tclThreadJoin.c [new file with mode: 0644]
tcl/library/dde/pkgIndex.tcl [new file with mode: 0644]

diff --git a/tcl/generic/tclThreadAlloc.c b/tcl/generic/tclThreadAlloc.c
new file mode 100644 (file)
index 0000000..54dbfde
--- /dev/null
@@ -0,0 +1,955 @@
+/*
+ * tclThreadAlloc.c --
+ *
+ *     This is a very fast storage allocator for used with threads (designed
+ *     avoid lock contention).  The basic strategy is to allocate memory in
+ *     fixed size blocks from block caches.
+ * 
+ * The Initial Developer of the Original Code is America Online, Inc.
+ * Portions created by AOL are Copyright (C) 1999 America Online, Inc.
+ *
+ * See the file "license.terms" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * RCS: @(#) $Id$ */
+
+#if defined(TCL_THREADS) && defined(USE_THREAD_ALLOC)
+
+#include "tclInt.h"
+
+#ifdef WIN32
+#include "tclWinInt.h"
+#else
+extern Tcl_Mutex *TclpNewAllocMutex(void);
+extern void *TclpGetAllocCache(void);
+extern void TclpSetAllocCache(void *);
+#endif
+
+/*
+ * If range checking is enabled, an additional byte will be allocated
+ * to store the magic number at the end of the requested memory.
+ */
+
+#ifndef RCHECK
+#ifdef  NDEBUG
+#define RCHECK         0
+#else
+#define RCHECK         1
+#endif
+#endif
+
+/*
+ * The following define the number of Tcl_Obj's to allocate/move
+ * at a time and the high water mark to prune a per-thread cache.
+ * On a 32 bit system, sizeof(Tcl_Obj) = 24 so 800 * 24 = ~16k.
+ *
+ */
+#define NOBJALLOC       800
+#define NOBJHIGH       1200
+
+/*
+ * The following defines the number of buckets in the bucket
+ * cache and those block sizes from (1<<4) to (1<<(3+NBUCKETS))
+ */
+
+#define NBUCKETS         11
+#define MAXALLOC         16284
+
+/*
+ * The following union stores accounting information for
+ * each block including two small magic numbers and
+ * a bucket number when in use or a next pointer when
+ * free.  The original requested size (not including
+ * the Block overhead) is also maintained.
+ */
+typedef struct Block {
+    union {
+       struct Block *next;       /* Next in free list. */
+       struct {
+           unsigned char magic1; /* First magic number. */
+           unsigned char bucket; /* Bucket block allocated from. */
+           unsigned char unused; /* Padding. */
+           unsigned char magic2; /* Second magic number. */
+        } b_s;
+    } b_u;
+    size_t b_reqsize;            /* Requested allocation size. */
+} Block;
+#define b_next         b_u.next
+#define b_bucket       b_u.b_s.bucket
+#define b_magic1       b_u.b_s.magic1
+#define b_magic2       b_u.b_s.magic2
+#define MAGIC          0xef
+
+/*
+ * The following structure defines a bucket of blocks with
+ * various accounting and statistics information.
+ */
+
+typedef struct Bucket {
+    Block *firstPtr;
+    int nfree;
+    int nget;
+    int nput;
+    int nwait;
+    int nlock;
+    int nrequest;
+} Bucket;
+
+/*
+ * The following structure defines a cache of buckets and objs.
+ */
+
+typedef struct Cache {
+    struct Cache  *nextPtr;
+    Tcl_ThreadId   owner;
+    Tcl_Obj       *firstObjPtr;
+    int            nobjs;
+    int                   nsysalloc;
+    Bucket         buckets[NBUCKETS];
+} Cache;
+
+/*
+ * The following array specifies various per-bucket 
+ * limits and locks.  The values are statically initialized
+ * to avoid calculating them repeatedly.
+ */
+
+struct binfo {
+    size_t blocksize;  /* Bucket blocksize. */
+    int maxblocks;     /* Max blocks before move to share. */
+    int nmove;         /* Num blocks to move to share. */
+    Tcl_Mutex *lockPtr; /* Share bucket lock. */
+} binfo[NBUCKETS] = {
+    {   16, 1024, 512, NULL},
+    {   32,  512, 256, NULL},
+    {   64,  256, 128, NULL},
+    {  128,  128,  64, NULL},
+    {  256,   64,  32, NULL},
+    {  512,   32,  16, NULL},
+    { 1024,   16,   8, NULL},
+    { 2048,    8,   4, NULL},
+    { 4096,    4,   2, NULL},
+    { 8192,    2,   1, NULL},
+    {16284,    1,   1, NULL},
+};
+
+/*
+ * Static functions defined in this file.
+ */
+
+static void LockBucket(Cache *cachePtr, int bucket);
+static void UnlockBucket(Cache *cachePtr, int bucket);
+static void PutBlocks(Cache *cachePtr, int bucket, int nmove);
+static int  GetBlocks(Cache *cachePtr, int bucket);
+static Block *Ptr2Block(char *ptr);
+static char *Block2Ptr(Block *blockPtr, int bucket, unsigned int reqsize);
+static void MoveObjs(Cache *fromPtr, Cache *toPtr, int nmove);
+
+/*
+ * Local variables defined in this file and initialized at
+ * startup.
+ */
+
+static Tcl_Mutex *listLockPtr;
+static Tcl_Mutex *objLockPtr;
+static Cache     sharedCache;
+static Cache    *sharedPtr = &sharedCache;
+static Cache    *firstCachePtr = &sharedCache;
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ *  GetCache ---
+ *
+ *     Gets per-thread memory cache, allocating it if necessary.
+ *
+ * Results:
+ *     Pointer to cache.
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static Cache *
+GetCache(void)
+{
+    Cache *cachePtr;
+
+    /*
+     * Check for first-time initialization.
+     */
+
+    if (listLockPtr == NULL) {
+       Tcl_Mutex *initLockPtr;
+       int i;
+
+       initLockPtr = Tcl_GetAllocMutex();
+       Tcl_MutexLock(initLockPtr);
+       if (listLockPtr == NULL) {
+           listLockPtr = TclpNewAllocMutex();
+           objLockPtr = TclpNewAllocMutex();
+           for (i = 0; i < NBUCKETS; ++i) {
+               binfo[i].lockPtr = TclpNewAllocMutex();
+           }
+       }
+       Tcl_MutexUnlock(initLockPtr);
+    }
+
+    /*
+     * Get this thread's cache, allocating if necessary.
+     */
+
+    cachePtr = TclpGetAllocCache();
+    if (cachePtr == NULL) {
+       cachePtr = calloc(1, sizeof(Cache));
+       if (cachePtr == NULL) {
+           panic("alloc: could not allocate new cache");
+       }
+       Tcl_MutexLock(listLockPtr);
+       cachePtr->nextPtr = firstCachePtr;
+       firstCachePtr = cachePtr;
+       Tcl_MutexUnlock(listLockPtr);
+       cachePtr->owner = Tcl_GetCurrentThread();
+       TclpSetAllocCache(cachePtr);
+    }
+    return cachePtr;
+}
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ *  TclFreeAllocCache --
+ *
+ *     Flush and delete a cache, removing from list of caches.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclFreeAllocCache(void *arg)
+{
+    Cache *cachePtr = arg;
+    Cache **nextPtrPtr;
+    register int   bucket;
+
+    /*
+     * Flush blocks.
+     */
+
+    for (bucket = 0; bucket < NBUCKETS; ++bucket) {
+       if (cachePtr->buckets[bucket].nfree > 0) {
+           PutBlocks(cachePtr, bucket, cachePtr->buckets[bucket].nfree);
+       }
+    }
+
+    /*
+     * Flush objs.
+     */
+
+    if (cachePtr->nobjs > 0) {
+       Tcl_MutexLock(objLockPtr);
+       MoveObjs(cachePtr, sharedPtr, cachePtr->nobjs);
+       Tcl_MutexUnlock(objLockPtr);
+    }
+
+    /*
+     * Remove from pool list.
+     */
+
+    Tcl_MutexLock(listLockPtr);
+    nextPtrPtr = &firstCachePtr;
+    while (*nextPtrPtr != cachePtr) {
+       nextPtrPtr = &(*nextPtrPtr)->nextPtr;
+    }
+    *nextPtrPtr = cachePtr->nextPtr;
+    cachePtr->nextPtr = NULL;
+    Tcl_MutexUnlock(listLockPtr);
+#ifdef WIN32
+    TlsFree((DWORD) cachePtr);
+#else
+    free(cachePtr);
+#endif
+}
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ *  TclpAlloc --
+ *
+ *     Allocate memory.
+ *
+ * Results:
+ *     Pointer to memory just beyond Block pointer.
+ *
+ * Side effects:
+ *     May allocate more blocks for a bucket.
+ *
+ *----------------------------------------------------------------------
+ */
+
+char *
+TclpAlloc(unsigned int reqsize)
+{
+    Cache         *cachePtr = TclpGetAllocCache();
+    Block         *blockPtr;
+    register int   bucket;
+    size_t        size;
+
+    if (cachePtr == NULL) {
+       cachePtr = GetCache();
+    }
+    
+    /*
+     * Increment the requested size to include room for 
+     * the Block structure.  Call malloc() directly if the
+     * required amount is greater than the largest block,
+     * otherwise pop the smallest block large enough,
+     * allocating more blocks if necessary.
+     */
+
+    blockPtr = NULL;     
+    size = reqsize + sizeof(Block);
+#if RCHECK
+    ++size;
+#endif
+    if (size > MAXALLOC) {
+       bucket = NBUCKETS;
+       blockPtr = malloc(size);
+       if (blockPtr != NULL) {
+           cachePtr->nsysalloc += reqsize;
+       }
+    } else {
+       bucket = 0;
+       while (binfo[bucket].blocksize < size) {
+           ++bucket;
+       }
+       if (cachePtr->buckets[bucket].nfree || GetBlocks(cachePtr, bucket)) {
+           blockPtr = cachePtr->buckets[bucket].firstPtr;
+           cachePtr->buckets[bucket].firstPtr = blockPtr->b_next;
+           --cachePtr->buckets[bucket].nfree;
+           ++cachePtr->buckets[bucket].nget;
+           cachePtr->buckets[bucket].nrequest += reqsize;
+       }
+    }
+    if (blockPtr == NULL) {
+       return NULL;
+    }
+    return Block2Ptr(blockPtr, bucket, reqsize);
+}
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ *  TclpFree --
+ *
+ *     Return blocks to the thread block cache.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     May move blocks to shared cache.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclpFree(char *ptr)
+{
+    if (ptr != NULL) {
+       Cache  *cachePtr = TclpGetAllocCache();
+       Block *blockPtr;
+       int bucket;
+
+       if (cachePtr == NULL) {
+           cachePtr = GetCache();
+       }
+       /*
+        * Get the block back from the user pointer and
+        * call system free directly for large blocks.
+        * Otherwise, push the block back on the bucket and
+        * move blocks to the shared cache if there are now
+        * too many free.
+        */
+
+       blockPtr = Ptr2Block(ptr);
+       bucket = blockPtr->b_bucket;
+       if (bucket == NBUCKETS) {
+           cachePtr->nsysalloc -= blockPtr->b_reqsize;
+           free(blockPtr);
+       } else {
+           cachePtr->buckets[bucket].nrequest -= blockPtr->b_reqsize;
+           blockPtr->b_next = cachePtr->buckets[bucket].firstPtr;
+           cachePtr->buckets[bucket].firstPtr = blockPtr;
+           ++cachePtr->buckets[bucket].nfree;
+           ++cachePtr->buckets[bucket].nput;
+           if (cachePtr != sharedPtr &&
+                   cachePtr->buckets[bucket].nfree > binfo[bucket].maxblocks) {
+               PutBlocks(cachePtr, bucket, binfo[bucket].nmove);
+           }
+       }
+    }
+}
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ *  TclpRealloc --
+ *
+ *     Re-allocate memory to a larger or smaller size.
+ *
+ * Results:
+ *     Pointer to memory just beyond Block pointer.
+ *
+ * Side effects:
+ *     Previous memory, if any, may be freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+char *
+TclpRealloc(char *ptr, unsigned int reqsize)
+{
+    Cache *cachePtr = TclpGetAllocCache();
+    Block *blockPtr;
+    void *new;
+    size_t size, min;
+    int bucket;
+
+    if (ptr == NULL) {
+       return TclpAlloc(reqsize);
+    }
+
+    if (cachePtr == NULL) {
+       cachePtr = GetCache();
+    }
+
+    /*
+     * If the block is not a system block and fits in place,
+     * simply return the existing pointer.  Otherwise, if the block
+     * is a system block and the new size would also require a system
+     * block, call realloc() directly.
+     */
+
+    blockPtr = Ptr2Block(ptr);
+    size = reqsize + sizeof(Block);
+#if RCHECK
+    ++size;
+#endif
+    bucket = blockPtr->b_bucket;
+    if (bucket != NBUCKETS) {
+       if (bucket > 0) {
+           min = binfo[bucket-1].blocksize;
+       } else {
+           min = 0;
+       }
+       if (size > min && size <= binfo[bucket].blocksize) {
+           cachePtr->buckets[bucket].nrequest -= blockPtr->b_reqsize;
+           cachePtr->buckets[bucket].nrequest += reqsize;
+           return Block2Ptr(blockPtr, bucket, reqsize);
+       }
+    } else if (size > MAXALLOC) {
+       cachePtr->nsysalloc -= blockPtr->b_reqsize;
+       cachePtr->nsysalloc += reqsize;
+       blockPtr = realloc(blockPtr, size);
+       if (blockPtr == NULL) {
+           return NULL;
+       }
+       return Block2Ptr(blockPtr, NBUCKETS, reqsize);
+    }
+
+    /*
+     * Finally, perform an expensive malloc/copy/free.
+     */
+
+    new = TclpAlloc(reqsize);
+    if (new != NULL) {
+       if (reqsize > blockPtr->b_reqsize) {
+           reqsize = blockPtr->b_reqsize;
+       }
+       memcpy(new, ptr, reqsize);
+       TclpFree(ptr);
+    }
+    return new;
+}
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclThreadAllocObj --
+ *
+ *     Allocate a Tcl_Obj from the per-thread cache.
+ *
+ * Results:
+ *     Pointer to uninitialized Tcl_Obj.
+ *
+ * Side effects:
+ *     May move Tcl_Obj's from shared cached or allocate new Tcl_Obj's
+ *     if list is empty.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+TclThreadAllocObj(void)
+{
+    register Cache *cachePtr = TclpGetAllocCache();
+    register int nmove;
+    register Tcl_Obj *objPtr;
+    Tcl_Obj *newObjsPtr;
+
+    if (cachePtr == NULL) {
+       cachePtr = GetCache();
+    }
+
+    /*
+     * Get this thread's obj list structure and move
+     * or allocate new objs if necessary.
+     */
+     
+    if (cachePtr->nobjs == 0) {
+       Tcl_MutexLock(objLockPtr);
+       nmove = sharedPtr->nobjs;
+       if (nmove > 0) {
+           if (nmove > NOBJALLOC) {
+               nmove = NOBJALLOC;
+           }
+           MoveObjs(sharedPtr, cachePtr, nmove);
+       }
+       Tcl_MutexUnlock(objLockPtr);
+       if (cachePtr->nobjs == 0) {
+           cachePtr->nobjs = nmove = NOBJALLOC;
+           newObjsPtr = malloc(sizeof(Tcl_Obj) * nmove);
+           if (newObjsPtr == NULL) {
+               panic("alloc: could not allocate %d new objects", nmove);
+           }
+           while (--nmove >= 0) {
+               objPtr = &newObjsPtr[nmove];
+               objPtr->internalRep.otherValuePtr = cachePtr->firstObjPtr;
+               cachePtr->firstObjPtr = objPtr;
+           }
+       }
+    }
+
+    /*
+     * Pop the first object.
+     */
+
+    objPtr = cachePtr->firstObjPtr;
+    cachePtr->firstObjPtr = objPtr->internalRep.otherValuePtr;
+    --cachePtr->nobjs;
+    return objPtr;
+}
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclThreadFreeObj --
+ *
+ *     Return a free Tcl_Obj to the per-thread cache.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     May move free Tcl_Obj's to shared list upon hitting high
+ *     water mark.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclThreadFreeObj(Tcl_Obj *objPtr)
+{
+    Cache *cachePtr = TclpGetAllocCache();
+
+    if (cachePtr == NULL) {
+       cachePtr = GetCache();
+    }
+
+    /*
+     * Get this thread's list and push on the free Tcl_Obj.
+     */
+     
+    objPtr->internalRep.otherValuePtr = cachePtr->firstObjPtr;
+    cachePtr->firstObjPtr = objPtr;
+    ++cachePtr->nobjs;
+    
+    /*
+     * If the number of free objects has exceeded the high
+     * water mark, move some blocks to the shared list.
+     */
+     
+    if (cachePtr->nobjs > NOBJHIGH) {
+       Tcl_MutexLock(objLockPtr);
+       MoveObjs(cachePtr, sharedPtr, NOBJALLOC);
+       Tcl_MutexUnlock(objLockPtr);
+    }
+}
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_GetMemoryInfo --
+ *
+ *     Return a list-of-lists of memory stats.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     List appended to given dstring.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+Tcl_GetMemoryInfo(Tcl_DString *dsPtr)
+{
+    Cache *cachePtr;
+    char buf[200];
+    int n;
+
+    Tcl_MutexLock(listLockPtr);
+    cachePtr = firstCachePtr;
+    while (cachePtr != NULL) {
+       Tcl_DStringStartSublist(dsPtr);
+       if (cachePtr == sharedPtr) {
+           Tcl_DStringAppendElement(dsPtr, "shared");
+       } else {
+           sprintf(buf, "thread%d", (int) cachePtr->owner);
+           Tcl_DStringAppendElement(dsPtr, buf);
+       }
+       for (n = 0; n < NBUCKETS; ++n) {
+           sprintf(buf, "%d %d %d %d %d %d %d",
+               (int) binfo[n].blocksize,
+               cachePtr->buckets[n].nfree,
+               cachePtr->buckets[n].nget,
+               cachePtr->buckets[n].nput,
+               cachePtr->buckets[n].nrequest,
+               cachePtr->buckets[n].nlock,
+               cachePtr->buckets[n].nwait);
+           Tcl_DStringAppendElement(dsPtr, buf);
+       }
+       Tcl_DStringEndSublist(dsPtr);
+           cachePtr = cachePtr->nextPtr;
+    }
+    Tcl_MutexUnlock(listLockPtr);
+}
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * MoveObjs --
+ *
+ *     Move Tcl_Obj's between caches.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+MoveObjs(Cache *fromPtr, Cache *toPtr, int nmove)
+{
+    register Tcl_Obj *objPtr = fromPtr->firstObjPtr;
+    Tcl_Obj *fromFirstObjPtr = objPtr;
+
+    toPtr->nobjs += nmove;
+    fromPtr->nobjs -= nmove;
+
+    /*
+     * Find the last object to be moved; set the next one
+     * (the first one not to be moved) as the first object
+     * in the 'from' cache.
+     */
+
+    while (--nmove) {
+       objPtr = objPtr->internalRep.otherValuePtr;
+    }
+    fromPtr->firstObjPtr = objPtr->internalRep.otherValuePtr;    
+
+    /*
+     * Move all objects as a block - they are already linked to
+     * each other, we just have to update the first and last.
+     */
+
+    objPtr->internalRep.otherValuePtr = toPtr->firstObjPtr;
+    toPtr->firstObjPtr = fromFirstObjPtr;
+}
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ *  Block2Ptr, Ptr2Block --
+ *
+ *     Convert between internal blocks and user pointers.
+ *
+ * Results:
+ *     User pointer or internal block.
+ *
+ * Side effects:
+ *     Invalid blocks will abort the server.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static char *
+Block2Ptr(Block *blockPtr, int bucket, unsigned int reqsize) 
+{
+    register void *ptr;
+
+    blockPtr->b_magic1 = blockPtr->b_magic2 = MAGIC;
+    blockPtr->b_bucket = bucket;
+    blockPtr->b_reqsize = reqsize;
+    ptr = ((void *) (blockPtr + 1));
+#if RCHECK
+    ((unsigned char *)(ptr))[reqsize] = MAGIC;
+#endif
+    return (char *) ptr;
+}
+
+static Block *
+Ptr2Block(char *ptr)
+{
+    register Block *blockPtr;
+
+    blockPtr = (((Block *) ptr) - 1);
+    if (blockPtr->b_magic1 != MAGIC
+#if RCHECK
+       || ((unsigned char *) ptr)[blockPtr->b_reqsize] != MAGIC
+#endif
+       || blockPtr->b_magic2 != MAGIC) {
+       panic("alloc: invalid block: %p: %x %x %x\n",
+           blockPtr, blockPtr->b_magic1, blockPtr->b_magic2,
+           ((unsigned char *) ptr)[blockPtr->b_reqsize]);
+    }
+    return blockPtr;
+}
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ *  LockBucket, UnlockBucket --
+ *
+ *     Set/unset the lock to access a bucket in the shared cache.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     Lock activity and contention are monitored globally and on
+ *     a per-cache basis.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+LockBucket(Cache *cachePtr, int bucket)
+{
+#if 0
+    if (Tcl_MutexTryLock(binfo[bucket].lockPtr) != TCL_OK) {
+       Tcl_MutexLock(binfo[bucket].lockPtr);
+       ++cachePtr->buckets[bucket].nwait;
+       ++sharedPtr->buckets[bucket].nwait;
+    }
+#else
+    Tcl_MutexLock(binfo[bucket].lockPtr);
+#endif
+    ++cachePtr->buckets[bucket].nlock;
+    ++sharedPtr->buckets[bucket].nlock;
+}
+
+
+static void
+UnlockBucket(Cache *cachePtr, int bucket)
+{
+    Tcl_MutexUnlock(binfo[bucket].lockPtr);
+}
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ *  PutBlocks --
+ *
+ *     Return unused blocks to the shared cache.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+PutBlocks(Cache *cachePtr, int bucket, int nmove)
+{
+    register Block *lastPtr, *firstPtr;
+    register int n = nmove;
+
+    /*
+     * Before acquiring the lock, walk the block list to find
+     * the last block to be moved.
+     */
+
+    firstPtr = lastPtr = cachePtr->buckets[bucket].firstPtr;
+    while (--n > 0) {
+       lastPtr = lastPtr->b_next;
+    }
+    cachePtr->buckets[bucket].firstPtr = lastPtr->b_next;
+    cachePtr->buckets[bucket].nfree -= nmove;
+
+    /*
+     * Aquire the lock and place the list of blocks at the front
+     * of the shared cache bucket.
+     */
+
+    LockBucket(cachePtr, bucket);
+    lastPtr->b_next = sharedPtr->buckets[bucket].firstPtr;
+    sharedPtr->buckets[bucket].firstPtr = firstPtr;
+    sharedPtr->buckets[bucket].nfree += nmove;
+    UnlockBucket(cachePtr, bucket);
+}
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ *  GetBlocks --
+ *
+ *     Get more blocks for a bucket.
+ *
+ * Results:
+ *     1 if blocks where allocated, 0 otherwise.
+ *
+ * Side effects:
+ *     Cache may be filled with available blocks.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+GetBlocks(Cache *cachePtr, int bucket)
+{
+    register Block *blockPtr;
+    register int n;
+    register size_t size;
+
+    /*
+     * First, atttempt to move blocks from the shared cache.  Note
+     * the potentially dirty read of nfree before acquiring the lock
+     * which is a slight performance enhancement.  The value is
+     * verified after the lock is actually acquired.
+     */
+     
+    if (cachePtr != sharedPtr && sharedPtr->buckets[bucket].nfree > 0) {
+       LockBucket(cachePtr, bucket);
+       if (sharedPtr->buckets[bucket].nfree > 0) {
+
+           /*
+            * Either move the entire list or walk the list to find
+            * the last block to move.
+            */
+
+           n = binfo[bucket].nmove;
+           if (n >= sharedPtr->buckets[bucket].nfree) {
+               cachePtr->buckets[bucket].firstPtr =
+                   sharedPtr->buckets[bucket].firstPtr;
+               cachePtr->buckets[bucket].nfree =
+                   sharedPtr->buckets[bucket].nfree;
+               sharedPtr->buckets[bucket].firstPtr = NULL;
+               sharedPtr->buckets[bucket].nfree = 0;
+           } else {
+               blockPtr = sharedPtr->buckets[bucket].firstPtr;
+               cachePtr->buckets[bucket].firstPtr = blockPtr;
+               sharedPtr->buckets[bucket].nfree -= n;
+               cachePtr->buckets[bucket].nfree = n;
+               while (--n > 0) {
+                   blockPtr = blockPtr->b_next;
+               }
+               sharedPtr->buckets[bucket].firstPtr = blockPtr->b_next;
+               blockPtr->b_next = NULL;
+           }
+       }
+       UnlockBucket(cachePtr, bucket);
+    }
+    
+    if (cachePtr->buckets[bucket].nfree == 0) {
+
+       /*
+        * If no blocks could be moved from shared, first look for a
+        * larger block in this cache to split up.
+        */
+
+       blockPtr = NULL;
+       n = NBUCKETS;
+       size = 0; /* lint */
+       while (--n > bucket) {
+           if (cachePtr->buckets[n].nfree > 0) {
+               size = binfo[n].blocksize;
+               blockPtr = cachePtr->buckets[n].firstPtr;
+               cachePtr->buckets[n].firstPtr = blockPtr->b_next;
+               --cachePtr->buckets[n].nfree;
+               break;
+           }
+       }
+
+       /*
+        * Otherwise, allocate a big new block directly.
+        */
+
+       if (blockPtr == NULL) {
+           size = MAXALLOC;
+           blockPtr = malloc(size);
+           if (blockPtr == NULL) {
+               return 0;
+           }
+       }
+
+       /*
+        * Split the larger block into smaller blocks for this bucket.
+        */
+
+       n = size / binfo[bucket].blocksize;
+       cachePtr->buckets[bucket].nfree = n;
+       cachePtr->buckets[bucket].firstPtr = blockPtr;
+       while (--n > 0) {
+           blockPtr->b_next = (Block *) 
+               ((char *) blockPtr + binfo[bucket].blocksize);
+           blockPtr = blockPtr->b_next;
+       }
+       blockPtr->b_next = NULL;
+    }
+    return 1;
+}
+
+#endif /* TCL_THREADS */
diff --git a/tcl/generic/tclThreadJoin.c b/tcl/generic/tclThreadJoin.c
new file mode 100644 (file)
index 0000000..d06c4de
--- /dev/null
@@ -0,0 +1,311 @@
+/* 
+ * tclThreadJoin.c --
+ *
+ *     This file implements a platform independent emulation layer for
+ *     the handling of joinable threads. The Mac and Windows platforms
+ *     use this code to provide the functionality of joining threads.
+ *     This code is currently not necessary on Unix.
+ *
+ * Copyright (c) 2000 by Scriptics Corporation
+ *
+ * See the file "license.terms" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * RCS: @(#) $Id$
+ */
+
+#include "tclInt.h"
+
+#if defined(WIN32) || defined(MAC_TCL)
+
+/* The information about each joinable thread is remembered in a
+ * structure as defined below.
+ */
+
+typedef struct JoinableThread {
+  Tcl_ThreadId  id;                     /* The id of the joinable thread */
+  int           result;                 /* A place for the result after the
+                                        * demise of the thread */
+  int           done;                   /* Boolean flag. Initialized to 0
+                                        * and set to 1 after the exit of
+                                        * the thread. This allows a thread
+                                        * requesting a join to detect when
+                                        * waiting is not necessary. */
+  int           waitedUpon;             /* Boolean flag. Initialized to 0
+                                        * and set to 1 by the thread waiting
+                                        * for this one via Tcl_JoinThread.
+                                        * Used to lock any other thread
+                                        * trying to wait on this one.
+                                        */
+  Tcl_Mutex     threadMutex;            /* The mutex used to serialize access
+                                        * to this structure. */
+  Tcl_Condition cond;                   /* This is the condition a thread has
+                                        * to wait upon to get notified of the
+                                        * end of the described thread. It is
+                                        * signaled indirectly by
+                                        * Tcl_ExitThread. */
+  struct JoinableThread* nextThreadPtr; /* Reference to the next thread in the
+                                        * list of joinable threads */
+} JoinableThread;
+
+/* The following variable is used to maintain the global list of all
+ * joinable threads. Usage by a thread is allowed only if the
+ * thread acquired the 'joinMutex'.
+ */
+
+TCL_DECLARE_MUTEX(joinMutex)
+
+static JoinableThread* firstThreadPtr;
+
+
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclJoinThread --
+ *
+ *     This procedure waits for the exit of the thread with the specified
+ *     id and returns its result.
+ *
+ * Results:
+ *     A standard tcl result signaling the overall success/failure of the
+ *     operation and an integer result delivered by the thread which was
+ *     waited upon.
+ *
+ * Side effects:
+ *     Deallocates the memory allocated by TclRememberJoinableThread.
+ *     Removes the data associated to the thread waited upon from the
+ *     list of joinable threads.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TclJoinThread(id, result)
+    Tcl_ThreadId id;     /* The id of the thread to wait upon. */
+    int*         result; /* Reference to a location for the result
+                         * of the thread we are waiting upon. */
+{
+    /* Steps done here:
+     * i.    Acquire the joinMutex and search for the thread.
+     * ii.   Error out if it could not be found.
+     * iii.  If found, switch from exclusive access to the list to exclusive
+     *       access to the thread structure.
+     * iv.   Error out if some other is already waiting.
+     * v.    Skip the waiting part of the thread is already done.
+     * vi.   Wait for the thread to exit, mark it as waited upon too.
+     * vii.  Get the result form the structure, 
+     * viii. switch to exclusive access of the list,
+     * ix.   remove the structure from the list,
+     * x.    then switch back to exclusive access to the structure
+     * xi.   and delete it.
+     */
+
+    JoinableThread* threadPtr;
+
+    Tcl_MutexLock (&joinMutex);
+
+    for (threadPtr = firstThreadPtr;
+        (threadPtr != (JoinableThread*) NULL) && (threadPtr->id != id);
+        threadPtr = threadPtr->nextThreadPtr)
+        /* empty body */
+      ;
+
+    if (threadPtr == (JoinableThread*) NULL) {
+        /* Thread not found. Either not joinable, or already waited
+        * upon and exited. Whatever, an error is in order.
+        */
+
+      Tcl_MutexUnlock (&joinMutex);
+      return TCL_ERROR;
+    }
+
+    /* [1] If we don't lock the structure before giving up exclusive access
+     * to the list some other thread just completing its wait on the same
+     * thread can delete the structure from under us, leaving us with a
+     * dangling pointer.
+     */
+
+    Tcl_MutexLock   (&threadPtr->threadMutex);
+    Tcl_MutexUnlock (&joinMutex);
+
+    /* [2] Now that we have the structure mutex any other thread that just
+     * tries to delete structure will wait at location [3] until we are
+     * done with the structure. And in that case we are done with it
+     * rather quickly as 'waitedUpon' will be set and we will have to
+     * error out.
+     */
+
+    if (threadPtr->waitedUpon) {
+        Tcl_MutexUnlock (&threadPtr->threadMutex);
+       return TCL_ERROR;
+    }
+
+    /* We are waiting now, let other threads recognize this
+     */
+
+    threadPtr->waitedUpon = 1;
+
+    while (!threadPtr->done) {
+      Tcl_ConditionWait (&threadPtr->cond, &threadPtr->threadMutex, NULL);
+    }
+
+    /* We have to release the structure before trying to access the list
+     * again or we can run into deadlock with a thread at [1] (see above)
+     * because of us holding the structure and the other holding the list.
+     * There is no problem with dangling pointers here as 'waitedUpon == 1'
+     * is still valid and any other thread will error out and not come to
+     * this place. IOW, the fact that we are here also means that no other
+     * thread came here before us and is able to delete the structure.
+     */
+
+    Tcl_MutexUnlock (&threadPtr->threadMutex);
+    Tcl_MutexLock   (&joinMutex);
+
+    /* We have to search the list again as its structure may (may, almost
+     * certainly) have changed while we were waiting. Especially now is the
+     * time to compute the predecessor in the list. Any earlier result can
+     * be dangling by now.
+     */
+
+    if (firstThreadPtr == threadPtr) {
+        firstThreadPtr = threadPtr->nextThreadPtr;
+    } else {
+        JoinableThread* prevThreadPtr;
+
+       for (prevThreadPtr = firstThreadPtr;
+            prevThreadPtr->nextThreadPtr != threadPtr;
+            prevThreadPtr = prevThreadPtr->nextThreadPtr)
+           /* empty body */
+         ;
+
+       prevThreadPtr->nextThreadPtr = threadPtr->nextThreadPtr;
+    }
+
+    Tcl_MutexUnlock (&joinMutex);
+
+    /* [3] Now that the structure is not part of the list anymore no other
+     * thread can acquire its mutex from now on. But it is possible that
+     * another thread is still holding the mutex though, see location [2].
+     * So we have to acquire the mutex one more time to wait for that thread
+     * to finish. We can (and have to) release the mutex immediately.
+     */
+
+    Tcl_MutexLock   (&threadPtr->threadMutex);
+    Tcl_MutexUnlock (&threadPtr->threadMutex);
+
+    /* Copy the result to us, finalize the synchronisation objects, then
+     * free the structure and return.
+     */
+
+    *result = threadPtr->result;
+
+    Tcl_ConditionFinalize (&threadPtr->cond);
+    Tcl_MutexFinalize (&threadPtr->threadMutex);
+    ckfree ((VOID*) threadPtr);
+
+    return TCL_OK;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclRememberJoinableThread --
+ *
+ *     This procedure remebers a thread as joinable. Only a call to
+ *     TclJoinThread will remove the structre created (and initialized)
+ *     here. IOW, not waiting upon a joinable thread will cause memory
+ *     leaks.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     Allocates memory, adds it to the global list of all joinable
+ *     threads.
+ *
+ *----------------------------------------------------------------------
+ */
+
+VOID
+TclRememberJoinableThread(id)
+    Tcl_ThreadId id; /* The thread to remember as joinable */
+{
+    JoinableThread* threadPtr;
+
+    threadPtr = (JoinableThread*) ckalloc (sizeof (JoinableThread));
+    threadPtr->id          = id;
+    threadPtr->done        = 0;
+    threadPtr->waitedUpon  = 0;
+    threadPtr->threadMutex = (Tcl_Mutex) NULL;
+    threadPtr->cond        = (Tcl_Condition) NULL;
+
+    Tcl_MutexLock (&joinMutex);
+
+    threadPtr->nextThreadPtr = firstThreadPtr;
+    firstThreadPtr           = threadPtr;
+
+    Tcl_MutexUnlock (&joinMutex);
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclSignalExitThread --
+ *
+ *     This procedure signals that the specified thread is done with
+ *     its work. If the thread is joinable this signal is propagated
+ *     to the thread waiting upon it.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     Modifies the associated structure to hold the result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+VOID
+TclSignalExitThread(id,result)
+    Tcl_ThreadId id;     /* Id of the thread signaling its exit */
+    int          result; /* The result from the thread */
+{
+    JoinableThread* threadPtr;
+
+    Tcl_MutexLock (&joinMutex);
+
+    for (threadPtr = firstThreadPtr;
+        (threadPtr != (JoinableThread*) NULL) && (threadPtr->id != id);
+        threadPtr = threadPtr->nextThreadPtr)
+        /* empty body */
+      ;
+
+    if (threadPtr == (JoinableThread*) NULL) {
+        /* Thread not found. Not joinable. No problem, nothing to do.
+        */
+
+        Tcl_MutexUnlock (&joinMutex);
+       return;
+    }
+
+    /* Switch over the exclusive access from the list to the structure,
+     * then store the result, set the flag and notify the waiting thread,
+     * provided that it exists. The order of lock/unlock ensures that a
+     * thread entering 'TclJoinThread' will not interfere with us.
+     */
+
+    Tcl_MutexLock   (&threadPtr->threadMutex);
+    Tcl_MutexUnlock (&joinMutex);
+
+    threadPtr->done   = 1;
+    threadPtr->result = result;
+
+    if (threadPtr->waitedUpon) {
+      Tcl_ConditionNotify (&threadPtr->cond);
+    }
+
+    Tcl_MutexUnlock (&threadPtr->threadMutex);
+}
+
+#endif /* WIN32 || MAC_TCL */
diff --git a/tcl/library/dde/pkgIndex.tcl b/tcl/library/dde/pkgIndex.tcl
new file mode 100644 (file)
index 0000000..f045ad8
--- /dev/null
@@ -0,0 +1,6 @@
+if {![package vsatisfies [package provide Tcl] 8]} {return}
+if {[info exists tcl_platform(debug)]} {
+    package ifneeded dde 1.2 [list load [file join $dir tcldde12d.dll] dde]
+} else {
+    package ifneeded dde 1.2 [list load [file join $dir tcldde12.dll] dde]
+}