CFSet.c   [plain text]


/*
 * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*	CFSet.c
	Copyright 1998-2002, Apple, Inc. All rights reserved.
	Responsibility: Christopher Kane
*/

#include <CoreFoundation/CFSet.h>
#include "CFInternal.h"

const CFSetCallBacks kCFTypeSetCallBacks = {0, __CFTypeCollectionRetain, __CFTypeCollectionRelease, CFCopyDescription, CFEqual, CFHash};
const CFSetCallBacks kCFCopyStringSetCallBacks = {0, (void *)CFStringCreateCopy, __CFTypeCollectionRelease, CFCopyDescription, CFEqual, CFHash};
static const CFSetCallBacks __kCFNullSetCallBacks = {0, NULL, NULL, NULL, NULL, NULL};

static const uint32_t __CFSetCapacities[42] = {
    4, 8, 17, 29, 47, 76, 123, 199, 322, 521, 843, 1364, 2207, 3571, 5778, 9349,
    15127, 24476, 39603, 64079, 103682, 167761, 271443, 439204, 710647, 1149851, 1860498,
    3010349, 4870847, 7881196, 12752043, 20633239, 33385282, 54018521, 87403803, 141422324,
    228826127, 370248451, 599074578, 969323029, 1568397607, 2537720636U
};

static const uint32_t __CFSetBuckets[42] = {	// primes
    5, 11, 23, 41, 67, 113, 199, 317, 521, 839, 1361, 2207, 3571, 5779, 9349, 15121,
    24473, 39607, 64081, 103681, 167759, 271429, 439199, 710641, 1149857, 1860503, 3010349,
    4870843, 7881193, 12752029, 20633237, 33385273, 54018521, 87403763, 141422317, 228826121,
    370248451, 599074561, 969323023, 1568397599, 2537720629U, 4106118251U
};

CF_INLINE CFIndex __CFSetRoundUpCapacity(CFIndex capacity) {
    CFIndex idx;
    for (idx = 0; idx < 42 && __CFSetCapacities[idx] < (uint32_t)capacity; idx++);
    if (42 <= idx) HALT;
    return __CFSetCapacities[idx];
}

CF_INLINE CFIndex __CFSetNumBucketsForCapacity(CFIndex capacity) {
    CFIndex idx;
    for (idx = 0; idx < 42 && __CFSetCapacities[idx] < (uint32_t)capacity; idx++);
    if (42 <= idx) HALT;
    return __CFSetBuckets[idx];
}

enum {		/* Bits 1-0 */
    __kCFSetImmutable = 0,	/* unchangable and fixed capacity */
    __kCFSetMutable = 1,		/* changeable and variable capacity */
    __kCFSetFixedMutable = 3	/* changeable and fixed capacity */
};

enum {		/* Bits 5-4 (value), 3-2 (key) */
    __kCFSetHasNullCallBacks = 0,
    __kCFSetHasCFTypeCallBacks = 1,
    __kCFSetHasCustomCallBacks = 3	/* callbacks are at end of header */
};

// Under GC, we fudge the key/value memory in two ways
// First, if we had null callbacks or null for both retain/release, we use unscanned memory
// This means that if people were doing addValue:[xxx new] and never removing, well, that doesn't work
//
// Second, if we notice standard retain/release implementations we substitute scanned memory
// and zero out the retain/release callbacks.  This is fine, but when copying we need to restore them

enum {
    __kCFSetRestoreKeys =       (1 << 0),
    __kCFSetRestoreValues =     (1 << 1),
    __kCFSetRestoreStringKeys = (1 << 2),
    __kCFSetWeakKeys =          (1 << 3)
};

struct __CFSet {
    CFRuntimeBase _base;
    CFIndex _count;		/* number of values */
    CFIndex _capacity;		/* maximum number of values */
    CFIndex _bucketsNum;	/* number of slots */
    uintptr_t _marker;
    void *_context;		/* private */
    CFIndex _deletes;
    CFOptionFlags _xflags;      /* bits for GC */
    const void **_keys;		/* can be NULL if not allocated yet */
};

/* Bits 1-0 of the base reserved bits are used for mutability variety */
/* Bits 3-2 of the base reserved bits are used for key callback indicator bits */
/* Bits 5-4 of the base reserved bits are used for value callback indicator bits */
/* Bit 6 is special KVO actions bit */

CF_INLINE CFIndex __CFSetGetType(CFSetRef set) {
    return __CFBitfieldGetValue(((const CFRuntimeBase *)set)->_info, 1, 0);
}

CF_INLINE CFIndex __CFSetGetSizeOfType(CFIndex t) {
    CFIndex size = sizeof(struct __CFSet);
    if (__CFBitfieldGetValue(t, 3, 2) == __kCFSetHasCustomCallBacks) {
	size += sizeof(CFSetCallBacks);
    }
    return size;
}

CF_INLINE const CFSetCallBacks *__CFSetGetKeyCallBacks(CFSetRef set) {
    CFSetCallBacks *result = NULL;
    switch (__CFBitfieldGetValue(((const CFRuntimeBase *)set)->_info, 3, 2)) {
    case __kCFSetHasNullCallBacks:
	return &__kCFNullSetCallBacks;
    case __kCFSetHasCFTypeCallBacks:
	return &kCFTypeSetCallBacks;
    case __kCFSetHasCustomCallBacks:
	break;
    }
    result = (CFSetCallBacks *)((uint8_t *)set + sizeof(struct __CFSet));
    return result;
}

CF_INLINE bool __CFSetCallBacksMatchNull(const CFSetCallBacks *c) {
    return (NULL == c ||
	(c->retain == __kCFNullSetCallBacks.retain &&
	 c->release == __kCFNullSetCallBacks.release &&
	 c->copyDescription == __kCFNullSetCallBacks.copyDescription &&
	 c->equal == __kCFNullSetCallBacks.equal &&
	 c->hash == __kCFNullSetCallBacks.hash));
}

CF_INLINE bool __CFSetCallBacksMatchCFType(const CFSetCallBacks *c) {
    return (&kCFTypeSetCallBacks == c ||
	(c->retain == kCFTypeSetCallBacks.retain &&
	 c->release == kCFTypeSetCallBacks.release &&
	 c->copyDescription == kCFTypeSetCallBacks.copyDescription &&
	 c->equal == kCFTypeSetCallBacks.equal &&
	 c->hash == kCFTypeSetCallBacks.hash));
}

#define CF_OBJC_KVO_WILLCHANGE(obj, sel)
#define CF_OBJC_KVO_DIDCHANGE(obj, sel)

static CFIndex __CFSetFindBuckets1a(CFSetRef set, const void *key) {
    CFHashCode keyHash = (CFHashCode)key;
    const void **keys = set->_keys;
    uintptr_t marker = set->_marker;
    CFIndex probe = keyHash % set->_bucketsNum;
    CFIndex probeskip = 1;	// See RemoveValue() for notes before changing this value
    CFIndex start = probe;
    for (;;) {
	uintptr_t currKey = (uintptr_t)keys[probe];
	if (marker == currKey) {		/* empty */
	    return kCFNotFound;
	} else if (~marker == currKey) {	/* deleted */
	    /* do nothing */
	} else if (currKey == (uintptr_t)key) {
	    return probe;
	}
	probe = probe + probeskip;
	// This alternative to probe % buckets assumes that
	// probeskip is always positive and less than the
	// number of buckets.
	if (set->_bucketsNum <= probe) {
	    probe -= set->_bucketsNum;
	}
	if (start == probe) {
	    return kCFNotFound;
	}
    }
}

static CFIndex __CFSetFindBuckets1b(CFSetRef set, const void *key) {
    const CFSetCallBacks *cb = __CFSetGetKeyCallBacks(set);
    CFHashCode keyHash = cb->hash ? (CFHashCode)INVOKE_CALLBACK2(((CFHashCode (*)(const void *, void *))cb->hash), key, set->_context) : (CFHashCode)key;
    const void **keys = set->_keys;
    uintptr_t marker = set->_marker;
    CFIndex probe = keyHash % set->_bucketsNum;
    CFIndex probeskip = 1;	// See RemoveValue() for notes before changing this value
    CFIndex start = probe;
    for (;;) {
	uintptr_t currKey = (uintptr_t)keys[probe];
	if (marker == currKey) {		/* empty */
	    return kCFNotFound;
	} else if (~marker == currKey) {	/* deleted */
	    /* do nothing */
	} else if (currKey == (uintptr_t)key || (cb->equal && INVOKE_CALLBACK3((Boolean (*)(const void *, const void *, void*))cb->equal, (void *)currKey, key, set->_context))) {
	    return probe;
	}
	probe = probe + probeskip;
	// This alternative to probe % buckets assumes that
	// probeskip is always positive and less than the
	// number of buckets.
	if (set->_bucketsNum <= probe) {
	    probe -= set->_bucketsNum;
	}
	if (start == probe) {
	    return kCFNotFound;
	}
    }
}

static void __CFSetFindBuckets2(CFSetRef set, const void *key, CFIndex *match, CFIndex *nomatch) {
    const CFSetCallBacks *cb = __CFSetGetKeyCallBacks(set);
    CFHashCode keyHash = cb->hash ? (CFHashCode)INVOKE_CALLBACK2(((CFHashCode (*)(const void *, void *))cb->hash), key, set->_context) : (CFHashCode)key;
    const void **keys = set->_keys;
    uintptr_t marker = set->_marker;
    CFIndex probe = keyHash % set->_bucketsNum;
    CFIndex probeskip = 1;	// See RemoveValue() for notes before changing this value
    CFIndex start = probe;
    *match = kCFNotFound;
    *nomatch = kCFNotFound;
    for (;;) {
	uintptr_t currKey = (uintptr_t)keys[probe];
	if (marker == currKey) {		/* empty */
	    if (nomatch) *nomatch = probe;
	    return;
	} else if (~marker == currKey) {	/* deleted */
	    if (nomatch) {
		*nomatch = probe;
		nomatch = NULL;
	    }
	} else if (currKey == (uintptr_t)key || (cb->equal && INVOKE_CALLBACK3((Boolean (*)(const void *, const void *, void*))cb->equal, (void *)currKey, key, set->_context))) {
	    *match = probe;
	    return;
	}
	probe = probe + probeskip;
	// This alternative to probe % buckets assumes that
	// probeskip is always positive and less than the
	// number of buckets.
	if (set->_bucketsNum <= probe) {
	    probe -= set->_bucketsNum;
	}
	if (start == probe) {
	    return;
	}
    }
}

static void __CFSetFindNewMarker(CFSetRef set) {
    const void **keys = set->_keys;
    uintptr_t newMarker;
    CFIndex idx, nbuckets;
    bool hit;

    nbuckets = set->_bucketsNum;
    newMarker = set->_marker;
    do {
	newMarker--;
	hit = false;
	for (idx = 0; idx < nbuckets; idx++) {
	    if (newMarker == (uintptr_t)keys[idx] || ~newMarker == (uintptr_t)keys[idx]) {
		hit = true;
		break;
	    }
	}
    } while (hit);
    for (idx = 0; idx < nbuckets; idx++) {
	if (set->_marker == (uintptr_t)keys[idx]) {
	    keys[idx] = (const void *)newMarker;
	} else if (~set->_marker == (uintptr_t)keys[idx]) {
	    keys[idx] = (const void *)~newMarker;
	}
    }
    ((struct __CFSet *)set)->_marker = newMarker;
}

static bool __CFSetEqual(CFTypeRef cf1, CFTypeRef cf2) {
    CFSetRef set1 = (CFSetRef)cf1;
    CFSetRef set2 = (CFSetRef)cf2;
    const CFSetCallBacks *cb1, *cb2;
    const void **keys;
    CFIndex idx, nbuckets;
    if (set1 == set2) return true;
    if (set1->_count != set2->_count) return false;
    cb1 = __CFSetGetKeyCallBacks(set1);
    cb2 = __CFSetGetKeyCallBacks(set2);
    if (cb1->equal != cb2->equal) return false;
    if (0 == set1->_count) return true; /* after function comparison! */
    keys = set1->_keys;
    nbuckets = set1->_bucketsNum;
    for (idx = 0; idx < nbuckets; idx++) {
	if (set1->_marker != (uintptr_t)keys[idx] && ~set1->_marker != (uintptr_t)keys[idx]) {
	    const void *value;
	    if (!CFSetGetValueIfPresent(set2, keys[idx], &value)) return false;
	}
    }
    return true;
}

static CFHashCode __CFSetHash(CFTypeRef cf) {
    CFSetRef set = (CFSetRef)cf;
    return set->_count;
}

static CFStringRef __CFSetCopyDescription(CFTypeRef cf) {
    CFSetRef set = (CFSetRef)cf;
    CFAllocatorRef allocator;
    const CFSetCallBacks *cb;
    const void **keys;
    CFIndex idx, nbuckets;
    CFMutableStringRef result;
    cb = __CFSetGetKeyCallBacks(set);
    keys = set->_keys;
    nbuckets = set->_bucketsNum;
    allocator = CFGetAllocator(set);
    result = CFStringCreateMutable(allocator, 0);
    switch (__CFSetGetType(set)) {
    case __kCFSetImmutable:
	CFStringAppendFormat(result, NULL, CFSTR("<CFSet %p [%p]>{type = immutable, count = %u, capacity = %u, pairs = (\n"), cf, allocator, set->_count, set->_capacity);
	break;
    case __kCFSetFixedMutable:
	CFStringAppendFormat(result, NULL, CFSTR("<CFSet %p [%p]>{type = fixed-mutable, count = %u, capacity = %u, pairs = (\n"), cf, allocator, set->_count, set->_capacity);
	break;
    case __kCFSetMutable:
	CFStringAppendFormat(result, NULL, CFSTR("<CFSet %p [%p]>{type = mutable, count = %u, capacity = %u, pairs = (\n"), cf, allocator, set->_count, set->_capacity);
	break;
    }
    for (idx = 0; idx < nbuckets; idx++) {
	if (set->_marker != (uintptr_t)keys[idx] && ~set->_marker != (uintptr_t)keys[idx]) {
	    CFStringRef kDesc = NULL;
	    if (NULL != cb->copyDescription) {
		kDesc = (CFStringRef)INVOKE_CALLBACK2(((CFStringRef (*)(const void *, void *))cb->copyDescription), keys[idx], set->_context);
	    }
	    if (NULL != kDesc) {
		CFStringAppendFormat(result, NULL, CFSTR("\t%u : %@\n"), idx, kDesc);
		CFRelease(kDesc);
	    } else {
		CFStringAppendFormat(result, NULL, CFSTR("\t%u : <%p>\n"), idx, keys[idx]);
	    }
	}
    }
    CFStringAppend(result, CFSTR(")}"));
    return result;
}

static void __CFSetDeallocate(CFTypeRef cf) {
    CFMutableSetRef set = (CFMutableSetRef)cf;
    CFAllocatorRef allocator = __CFGetAllocator(set);
    if (CF_IS_COLLECTABLE_ALLOCATOR(allocator)) {
        const CFSetCallBacks *kcb = __CFSetGetKeyCallBacks(set);
        if (kcb->retain == NULL && kcb->release == NULL)
            return; // XXX_PCB keep set intact during finalization.
    }
    if (__CFSetGetType(set) == __kCFSetImmutable) {
        __CFBitfieldSetValue(((CFRuntimeBase *)set)->_info, 1, 0, __kCFSetFixedMutable);
    }

    const CFSetCallBacks *cb = __CFSetGetKeyCallBacks(set);
    if (cb->release) {
	const void **keys = set->_keys;
	CFIndex idx, nbuckets = set->_bucketsNum;
	for (idx = 0; idx < nbuckets; idx++) {
	    if (set->_marker != (uintptr_t)keys[idx] && ~set->_marker != (uintptr_t)keys[idx]) {
		const void *oldkey = keys[idx];
		INVOKE_CALLBACK3(((void (*)(CFAllocatorRef, const void *, void *))cb->release), allocator, oldkey, set->_context);
	    }
	}
    }

    if (__CFSetGetType(set) == __kCFSetMutable && set->_keys) {
        _CFAllocatorDeallocateGC(allocator, set->_keys);
        set->_keys = NULL;
    }
}

/*
 * When running under GC, we suss up sets with standard string copy to hold
 * onto everything, including the copies of incoming keys, in strong memory without retain counts.
 * This is the routine that makes that copy.
 * Not for inputs of constant strings we'll get a constant string back, and so the result
 * is not guaranteed to be from the auto zone, hence the call to CFRelease since it will figure
 * out where the refcount really is.
 */
static CFStringRef _CFStringCreateCopyCollected(CFAllocatorRef allocator, CFStringRef theString) {
    return CFMakeCollectable(CFStringCreateCopy(NULL, theString));
}

static CFTypeID __kCFSetTypeID = _kCFRuntimeNotATypeID;

static const CFRuntimeClass __CFSetClass = {
    _kCFRuntimeScannedObject,
    "CFSet",
    NULL,	// init
    NULL,	// copy
    __CFSetDeallocate,
    (void *)__CFSetEqual,
    __CFSetHash,
    NULL,	// 
    __CFSetCopyDescription
};

__private_extern__ void __CFSetInitialize(void) {
    __kCFSetTypeID = _CFRuntimeRegisterClass(&__CFSetClass);
}

CFTypeID CFSetGetTypeID(void) {
    return __kCFSetTypeID;
}

static CFSetRef __CFSetInit(CFAllocatorRef allocator, uint32_t flags, CFIndex capacity, const CFSetCallBacks *keyCallBacks) {
    struct __CFSet *memory;
    uint32_t size;
    CFIndex idx;
    __CFBitfieldSetValue(flags, 31, 2, 0);
    CFSetCallBacks nonRetainingKeyCallbacks;
    CFOptionFlags xflags = 0;
    if (CF_IS_COLLECTABLE_ALLOCATOR(allocator)) {
        // preserve NULL for key or value CB, otherwise fix up.
        if (!keyCallBacks || (keyCallBacks->retain == NULL && keyCallBacks->release == NULL)) {
	    xflags = __kCFSetWeakKeys;
	}
        else {
	    if (keyCallBacks->retain == __CFTypeCollectionRetain && keyCallBacks->release == __CFTypeCollectionRelease) {
                // copy everything
                nonRetainingKeyCallbacks = *keyCallBacks;
                nonRetainingKeyCallbacks.retain = NULL;
                nonRetainingKeyCallbacks.release = NULL;
                keyCallBacks = &nonRetainingKeyCallbacks;
		xflags = __kCFSetRestoreKeys;
            }
	    else if (keyCallBacks->retain == CFStringCreateCopy && keyCallBacks->release == __CFTypeCollectionRelease) {
                // copy everything
                nonRetainingKeyCallbacks = *keyCallBacks;
                nonRetainingKeyCallbacks.retain = (void *)_CFStringCreateCopyCollected;   // XXX fix with better cast
                nonRetainingKeyCallbacks.release = NULL;
                keyCallBacks = &nonRetainingKeyCallbacks;
		xflags = (__kCFSetRestoreKeys | __kCFSetRestoreStringKeys);
            }
        }
    }
    if (__CFSetCallBacksMatchNull(keyCallBacks)) {
	__CFBitfieldSetValue(flags, 3, 2, __kCFSetHasNullCallBacks);
    } else if (__CFSetCallBacksMatchCFType(keyCallBacks)) {
	__CFBitfieldSetValue(flags, 3, 2, __kCFSetHasCFTypeCallBacks);
    } else {
	__CFBitfieldSetValue(flags, 3, 2, __kCFSetHasCustomCallBacks);
    }
    size = __CFSetGetSizeOfType(flags) - sizeof(CFRuntimeBase);
    switch (__CFBitfieldGetValue(flags, 1, 0)) {
    case __kCFSetImmutable:
    case __kCFSetFixedMutable:
        size += __CFSetNumBucketsForCapacity(capacity) * sizeof(const void *);
        break;
    case __kCFSetMutable:
        break;
    }
    memory = (struct __CFSet *)_CFRuntimeCreateInstance(allocator, __kCFSetTypeID, size, NULL);
    if (NULL == memory) {
	return NULL;
    }
    __CFBitfieldSetValue(memory->_base._info, 6, 0, flags);
    memory->_count = 0;
    memory->_marker = (uintptr_t)0xa1b1c1d3;
    memory->_context = NULL;
    memory->_deletes = 0;
    memory->_xflags = xflags;
    switch (__CFBitfieldGetValue(flags, 1, 0)) {
    case __kCFSetImmutable:
        if (CF_IS_COLLECTABLE_ALLOCATOR(allocator) && (xflags & __kCFSetWeakKeys)) { // if weak, don't scan
            auto_zone_set_layout_type(__CFCollectableZone, memory, AUTO_OBJECT_UNSCANNED);
        }
        if (__CFOASafe) __CFSetLastAllocationEventName(memory, "CFSet (immutable)");
	memory->_capacity = capacity;	/* Don't round up capacity */
	memory->_bucketsNum = __CFSetNumBucketsForCapacity(memory->_capacity);
	memory->_keys = (const void **)((uint8_t *)memory + __CFSetGetSizeOfType(flags));
	for (idx = memory->_bucketsNum; idx--;) {
	    memory->_keys[idx] = (const void *)memory->_marker;
	}
	break;
    case __kCFSetFixedMutable:
	if (CF_IS_COLLECTABLE_ALLOCATOR(allocator) && (xflags & __kCFSetWeakKeys)) { // if weak, don't scan
	    auto_zone_set_layout_type(__CFCollectableZone, memory, AUTO_OBJECT_UNSCANNED);
	}
	if (__CFOASafe) __CFSetLastAllocationEventName(memory, "CFSet (mutable-fixed)");
	memory->_capacity = capacity;	/* Don't round up capacity */
	memory->_bucketsNum = __CFSetNumBucketsForCapacity(memory->_capacity);
	memory->_keys = (const void **)((uint8_t *)memory + __CFSetGetSizeOfType(flags));
	for (idx = memory->_bucketsNum; idx--;) {
	    memory->_keys[idx] = (const void *)memory->_marker;
	}
	break;
    case __kCFSetMutable:
	if (__CFOASafe) __CFSetLastAllocationEventName(memory, "CFSet (mutable-variable)");
	memory->_capacity = __CFSetRoundUpCapacity(1);
	memory->_bucketsNum = 0;
	memory->_keys = NULL;
	break;
    }
    if (__kCFSetHasCustomCallBacks == __CFBitfieldGetValue(flags, 3, 2)) {
	CFSetCallBacks *cb = (CFSetCallBacks *)__CFSetGetKeyCallBacks((CFSetRef)memory);
	*cb = *keyCallBacks;
	FAULT_CALLBACK((void **)&(cb->retain));
	FAULT_CALLBACK((void **)&(cb->release));
	FAULT_CALLBACK((void **)&(cb->copyDescription));
	FAULT_CALLBACK((void **)&(cb->equal));
	FAULT_CALLBACK((void **)&(cb->hash));
    }
    return (CFSetRef)memory;
}

CFSetRef CFSetCreate(CFAllocatorRef allocator, const void **keys, CFIndex numValues, const CFSetCallBacks *keyCallBacks) {
    CFSetRef result;
    uint32_t flags;
    CFIndex idx;
    CFAssert2(0 <= numValues, __kCFLogAssertion, "%s(): numValues (%d) cannot be less than zero", __PRETTY_FUNCTION__, numValues);
    result = __CFSetInit(allocator, __kCFSetImmutable, numValues, keyCallBacks);
    flags = __CFBitfieldGetValue(((const CFRuntimeBase *)result)->_info, 1, 0);
    if (flags == __kCFSetImmutable) {
	// tweak flags so that we can add our immutable values
        __CFBitfieldSetValue(((CFRuntimeBase *)result)->_info, 1, 0, __kCFSetFixedMutable);
    }
    for (idx = 0; idx < numValues; idx++) {
	CFSetAddValue((CFMutableSetRef)result, keys[idx]);
    }
    __CFBitfieldSetValue(((CFRuntimeBase *)result)->_info, 1, 0, flags);
    return result;
}

CFMutableSetRef CFSetCreateMutable(CFAllocatorRef allocator, CFIndex capacity, const CFSetCallBacks *keyCallBacks) {
    CFAssert2(0 <= capacity, __kCFLogAssertion, "%s(): capacity (%d) cannot be less than zero", __PRETTY_FUNCTION__, capacity);
    return (CFMutableSetRef)__CFSetInit(allocator, (0 == capacity) ? __kCFSetMutable : __kCFSetFixedMutable, capacity, keyCallBacks);
}


static void __CFSetGrow(CFMutableSetRef set, CFIndex numNewValues);

// This creates a set which is for CFTypes or NSObjects, with an ownership transfer -- 
// the set does not take a retain, and the caller does not need to release the inserted objects.
// The incoming objects must also be collectable if allocated out of a collectable allocator.
CFSetRef _CFSetCreate_ex(CFAllocatorRef allocator, bool mutable, const void **keys, CFIndex numValues) {
    CFSetRef result;
    void *bucketsBase;
    uint32_t flags;
    CFIndex idx;
    result = __CFSetInit(allocator, mutable ? __kCFSetMutable : __kCFSetImmutable, numValues, &kCFTypeSetCallBacks);
    flags = __CFBitfieldGetValue(((const CFRuntimeBase *)result)->_info, 1, 0);
    if (!mutable) {
        __CFBitfieldSetValue(((CFRuntimeBase *)result)->_info, 1, 0, __kCFSetFixedMutable);
    }
    if (mutable) {
	if (result->_count == result->_capacity || NULL == result->_keys) {
	    __CFSetGrow((CFMutableSetRef)result, numValues);
	}
    }
    // GC:  since kCFTypeSetCallBacks are used, the keys
    // and values will be allocated contiguously.
    bool collectableContainer = CF_IS_COLLECTABLE_ALLOCATOR(allocator);
    bucketsBase = (collectableContainer ? (void *)auto_zone_base_pointer(__CFCollectableZone, result->_keys) : NULL);
    for (idx = 0; idx < numValues; idx++) {
	CFIndex match, nomatch;
	const void *newKey;
	__CFSetFindBuckets2(result, keys[idx], &match, &nomatch);
	if (kCFNotFound != match) {
	} else {
	    newKey = keys[idx];
	    if (result->_marker == (uintptr_t)newKey || ~result->_marker == (uintptr_t)newKey) {
		__CFSetFindNewMarker(result);
	    }
	    CF_WRITE_BARRIER_BASE_ASSIGN(allocator, bucketsBase, result->_keys[nomatch], newKey);
	    // GC: generation(_keys/_values) <= generation(keys/values), but added for completeness.
	    ((CFMutableSetRef)result)->_count++;
	}
    }
    __CFBitfieldSetValue(((CFRuntimeBase *)result)->_info, 1, 0, flags);
    return result;
}

CFSetRef CFSetCreateCopy(CFAllocatorRef allocator, CFSetRef set) {
    CFSetRef result;
    const CFSetCallBacks *cb;
    CFIndex numValues = CFSetGetCount(set);
    const void **list, *buffer[256];
    list = (numValues <= 256) ? buffer : CFAllocatorAllocate(allocator, numValues * sizeof(void *), 0); // XXX_PCB GC OK
    if (list != buffer && __CFOASafe) __CFSetLastAllocationEventName(list, "CFSet (temp)");
    CFSetGetValues(set, list);
    CFSetCallBacks patchedKeyCB;
    if (CF_IS_OBJC(__kCFSetTypeID, set)) {
	cb = &kCFTypeSetCallBacks;
    }
    else {
	cb = __CFSetGetKeyCallBacks(set);
	if (CF_IS_COLLECTABLE_ALLOCATOR(allocator)) {
	    if (set->_xflags & __kCFSetRestoreKeys) {
		patchedKeyCB = *cb;    // copy
		cb = &patchedKeyCB;    // reset to copy
		patchedKeyCB.retain = (set->_xflags & __kCFSetRestoreStringKeys) ? CFStringCreateCopy : __CFTypeCollectionRetain;
		patchedKeyCB.release = __CFTypeCollectionRelease;
	    }
	}
    }
    result = CFSetCreate(allocator, list, numValues, cb);
    if (list != buffer) CFAllocatorDeallocate(allocator, list); // GC OK
    return result;
}

CFMutableSetRef CFSetCreateMutableCopy(CFAllocatorRef allocator, CFIndex capacity, CFSetRef set) {
    CFMutableSetRef result;
    const CFSetCallBacks *cb;
    CFIndex idx, numValues = CFSetGetCount(set);
    const void **list, *buffer[256];
    CFAssert3(0 == capacity || numValues <= capacity, __kCFLogAssertion, "%s(): for fixed-mutable sets, capacity (%d) must be greater than or equal to initial number of values (%d)", __PRETTY_FUNCTION__, capacity, numValues);
    list = (numValues <= 256) ? buffer : CFAllocatorAllocate(allocator, numValues * sizeof(void *), 0); // XXX_PCB GC OK
    if (list != buffer && __CFOASafe) __CFSetLastAllocationEventName(list, "CFSet (temp)");
    CFSetGetValues(set, list);
    CFSetCallBacks patchedKeyCB;
    if (CF_IS_OBJC(__kCFSetTypeID, set)) {
	cb = &kCFTypeSetCallBacks;
    }
    else {
	cb = __CFSetGetKeyCallBacks(set);
	if (CF_IS_COLLECTABLE_ALLOCATOR(allocator)) {
	    if (set->_xflags & __kCFSetRestoreKeys) {
		patchedKeyCB = *cb;    // copy
		cb = &patchedKeyCB;    // reset to copy
		patchedKeyCB.retain = (set->_xflags & __kCFSetRestoreStringKeys) ? CFStringCreateCopy : __CFTypeCollectionRetain;
		patchedKeyCB.release = __CFTypeCollectionRelease;
	    }
	}
    }
    result = CFSetCreateMutable(allocator, capacity, cb);
    if (0 == capacity) _CFSetSetCapacity(result, numValues);
    for (idx = 0; idx < numValues; idx++) {
	CFSetAddValue(result, list[idx]);
    }
    if (list != buffer) CFAllocatorDeallocate(allocator, list); // XXX_PCB GC OK
    return result;
}

// Used by NSHashTables and KVO
void _CFSetSetContext(CFSetRef set, void *context) {
    CFAllocatorRef allocator = CFGetAllocator(set);
    CF_WRITE_BARRIER_BASE_ASSIGN(allocator, set, set->_context, context);
}

void *_CFSetGetContext(CFSetRef set) {
    return ((struct __CFSet *)set)->_context;
}

CFIndex CFSetGetCount(CFSetRef set) {
    CF_OBJC_FUNCDISPATCH0(__kCFSetTypeID, CFIndex, set, "count");
    __CFGenericValidateType(set, __kCFSetTypeID);
    return set->_count;
}

CFIndex CFSetGetCountOfValue(CFSetRef set, const void *key) {
    CFIndex match;
    CF_OBJC_FUNCDISPATCH1(__kCFSetTypeID, CFIndex, set, "countForObject:", key);
    __CFGenericValidateType(set, __kCFSetTypeID);
    if (0 == set->_count) return 0;
    if (__kCFSetHasNullCallBacks == __CFBitfieldGetValue(((const CFRuntimeBase *)set)->_info, 3, 2)) {
	match = __CFSetFindBuckets1a(set, key);
    } else {
	match = __CFSetFindBuckets1b(set, key);
    }
    return (kCFNotFound != match ? 1 : 0);
}

Boolean CFSetContainsValue(CFSetRef set, const void *key) {
    CFIndex match;
    CF_OBJC_FUNCDISPATCH1(__kCFSetTypeID, char, set, "containsObject:", key);
    __CFGenericValidateType(set, __kCFSetTypeID);
    if (0 == set->_count) return false;
    if (__kCFSetHasNullCallBacks == __CFBitfieldGetValue(((const CFRuntimeBase *)set)->_info, 3, 2)) {
	match = __CFSetFindBuckets1a(set, key);
    } else {
	match = __CFSetFindBuckets1b(set, key);
    }
    return (kCFNotFound != match ? true : false);
}

const void *CFSetGetValue(CFSetRef set, const void *key) {
    CFIndex match;
    CF_OBJC_FUNCDISPATCH1(__kCFSetTypeID, const void *, set, "member:", key);
    __CFGenericValidateType(set, __kCFSetTypeID);
    if (0 == set->_count) return NULL;
    if (__kCFSetHasNullCallBacks == __CFBitfieldGetValue(((const CFRuntimeBase *)set)->_info, 3, 2)) {
	match = __CFSetFindBuckets1a(set, key);
    } else {
	match = __CFSetFindBuckets1b(set, key);
    }
    return (kCFNotFound != match ? set->_keys[match] : NULL);
}

Boolean CFSetGetValueIfPresent(CFSetRef set, const void *key, const void **actualkey) {
    CFIndex match;
    CF_OBJC_FUNCDISPATCH2(__kCFSetTypeID, char, set, "_getValue:forObj:", (void * *) actualkey, key);
    __CFGenericValidateType(set, __kCFSetTypeID);
    if (0 == set->_count) return false;
    if (__kCFSetHasNullCallBacks == __CFBitfieldGetValue(((const CFRuntimeBase *)set)->_info, 3, 2)) {
	match = __CFSetFindBuckets1a(set, key);
    } else {
	match = __CFSetFindBuckets1b(set, key);
    }
    return (kCFNotFound != match ? ((actualkey ? __CFObjCStrongAssign(set->_keys[match], actualkey) : NULL), true) : false);
}

void CFSetGetValues(CFSetRef set, const void **keys) {
    CFIndex idx, cnt, nbuckets;
    CF_OBJC_FUNCDISPATCH1(__kCFSetTypeID, void, set, "getObjects:", (void * *) keys);
    __CFGenericValidateType(set, __kCFSetTypeID);
    if (CF_USING_COLLECTABLE_MEMORY) {
	// GC: speculatively issue a write-barrier on the copied to buffers (3743553).
	__CFObjCWriteBarrierRange(keys, set->_count * sizeof(void *));
    }
    nbuckets = set->_bucketsNum;
    for (idx = 0; idx < nbuckets; idx++) {
	if (set->_marker != (uintptr_t)set->_keys[idx] && ~set->_marker != (uintptr_t)set->_keys[idx]) {
	    for (cnt = 1; cnt--;) {
		if (keys) CF_WRITE_BARRIER_ASSIGN(NULL, *keys++, set->_keys[idx]);
	    }
	}
    }
}

void CFSetApplyFunction(CFSetRef set, CFSetApplierFunction applier, void *context) {
    const void **keys;
    CFIndex idx, cnt, nbuckets;
    FAULT_CALLBACK((void **)&(applier));
    CF_OBJC_FUNCDISPATCH2(__kCFSetTypeID, void, set, "_applyValues:context:", applier, context);
    __CFGenericValidateType(set, __kCFSetTypeID);
    keys = set->_keys;
    nbuckets = set->_bucketsNum;
    for (idx = 0; idx < nbuckets; idx++) {
	if (set->_marker != (uintptr_t)keys[idx] && ~set->_marker != (uintptr_t)keys[idx]) {
	    for (cnt = 1; cnt--;) {
		INVOKE_CALLBACK2(applier, keys[idx], context);
	    }
	}
    }
}


static void __CFSetGrow(CFMutableSetRef set, CFIndex numNewValues) {
    const void **oldkeys = set->_keys;
    CFIndex idx, oldnbuckets = set->_bucketsNum;
    CFIndex oldCount = set->_count;
    CFAllocatorRef allocator = __CFGetAllocator(set), keysAllocator;
    void *keysBase;
    set->_capacity = __CFSetRoundUpCapacity(oldCount + numNewValues);
    set->_bucketsNum = __CFSetNumBucketsForCapacity(set->_capacity);
    set->_deletes = 0;
    void *buckets = _CFAllocatorAllocateGC(allocator, set->_bucketsNum * sizeof(const void *), (set->_xflags & __kCFSetWeakKeys) ? AUTO_MEMORY_UNSCANNED : AUTO_MEMORY_SCANNED);
    CF_WRITE_BARRIER_BASE_ASSIGN(allocator, set, set->_keys, buckets);
    keysAllocator = allocator;
    keysBase = set->_keys;
    if (NULL == set->_keys) HALT;
    if (__CFOASafe) __CFSetLastAllocationEventName(set->_keys, "CFSet (store)");
    for (idx = set->_bucketsNum; idx--;) {
        set->_keys[idx] = (const void *)set->_marker;
    }
    if (NULL == oldkeys) return;
    for (idx = 0; idx < oldnbuckets; idx++) {
        if (set->_marker != (uintptr_t)oldkeys[idx] && ~set->_marker != (uintptr_t)oldkeys[idx]) {
            CFIndex match, nomatch;
            __CFSetFindBuckets2(set, oldkeys[idx], &match, &nomatch);
            CFAssert3(kCFNotFound == match, __kCFLogAssertion, "%s(): two values (%p, %p) now hash to the same slot; mutable value changed while in table or hash value is not immutable", __PRETTY_FUNCTION__, oldkeys[idx], set->_keys[match]);
            if (kCFNotFound != nomatch) {
                CF_WRITE_BARRIER_BASE_ASSIGN(keysAllocator, keysBase, set->_keys[nomatch], oldkeys[idx]);
            }
        }
    }
    CFAssert1(set->_count == oldCount, __kCFLogAssertion, "%s(): set count differs after rehashing; error", __PRETTY_FUNCTION__);
    _CFAllocatorDeallocateGC(allocator, oldkeys);
}

// This function is for Foundation's benefit; no one else should use it.
void _CFSetSetCapacity(CFMutableSetRef set, CFIndex cap) {
    if (CF_IS_OBJC(__kCFSetTypeID, set)) return;
#if defined(DEBUG)
    __CFGenericValidateType(set, __kCFSetTypeID);
    CFAssert1(__CFSetGetType(set) != __kCFSetImmutable && __CFSetGetType(set) != __kCFSetFixedMutable, __kCFLogAssertion, "%s(): set is immutable or fixed-mutable", __PRETTY_FUNCTION__);
    CFAssert3(set->_count <= cap, __kCFLogAssertion, "%s(): desired capacity (%d) is less than count (%d)", __PRETTY_FUNCTION__, cap, set->_count);
#endif
    __CFSetGrow(set, cap - set->_count);
}


void CFSetAddValue(CFMutableSetRef set, const void *key) {
    CFIndex match, nomatch;
    const CFSetCallBacks *cb;
    const void *newKey;
    CF_OBJC_FUNCDISPATCH1(__kCFSetTypeID, void, set, "addObject:", key);
    __CFGenericValidateType(set, __kCFSetTypeID);
    switch (__CFSetGetType(set)) {
    case __kCFSetMutable:
	if (set->_count == set->_capacity || NULL == set->_keys) {
	    __CFSetGrow(set, 1);
	}
	break;
    case __kCFSetFixedMutable:
	CFAssert3(set->_count < set->_capacity, __kCFLogAssertion, "%s(): capacity exceeded on fixed-capacity set %p (capacity = %d)", __PRETTY_FUNCTION__, set, set->_capacity);
	break;
    default:
	CFAssert2(__CFSetGetType(set) != __kCFSetImmutable, __kCFLogAssertion, "%s(): immutable set %p passed to mutating operation", __PRETTY_FUNCTION__, set);
	break;
    }
    __CFSetFindBuckets2(set, key, &match, &nomatch);
    if (kCFNotFound != match) {
    } else {
        CFAllocatorRef allocator = __CFGetAllocator(set);
        CFAllocatorRef keysAllocator = (set->_xflags & __kCFSetWeakKeys) ? kCFAllocatorNull : allocator;
	cb = __CFSetGetKeyCallBacks(set);
	if (cb->retain) {
	    newKey = (void *)INVOKE_CALLBACK3(((const void *(*)(CFAllocatorRef, const void *, void *))cb->retain), allocator, key, set->_context);
	} else {
	    newKey = key;
	}
	if (set->_marker == (uintptr_t)newKey || ~set->_marker == (uintptr_t)newKey) {
	    __CFSetFindNewMarker(set);
	}
	CF_OBJC_KVO_WILLCHANGE(set, key);
	CF_WRITE_BARRIER_ASSIGN(keysAllocator, set->_keys[nomatch], newKey);
	set->_count++;
	CF_OBJC_KVO_DIDCHANGE(set, key);
    }
}

__private_extern__ const void *__CFSetAddValueAndReturn(CFMutableSetRef set, const void *key) {
    CFIndex match, nomatch;
    const CFSetCallBacks *cb;
    const void *newKey;
// #warning not toll-free bridged, but internal
    __CFGenericValidateType(set, __kCFSetTypeID);
    switch (__CFSetGetType(set)) {
    case __kCFSetMutable:
	if (set->_count == set->_capacity || NULL == set->_keys) {
	    __CFSetGrow(set, 1);
	}
	break;
    case __kCFSetFixedMutable:
	CFAssert3(set->_count < set->_capacity, __kCFLogAssertion, "%s(): capacity exceeded on fixed-capacity set %p (capacity = %d)", __PRETTY_FUNCTION__, set, set->_capacity);
	break;
    default:
	CFAssert2(__CFSetGetType(set) != __kCFSetImmutable, __kCFLogAssertion, "%s(): immutable set %p passed to mutating operation", __PRETTY_FUNCTION__, set);
	break;
    }
    __CFSetFindBuckets2(set, key, &match, &nomatch);
    if (kCFNotFound != match) {
	return set->_keys[match];
    } else {
        CFAllocatorRef allocator = __CFGetAllocator(set);
        CFAllocatorRef keysAllocator = (set->_xflags & __kCFSetWeakKeys) ? kCFAllocatorNull : allocator;
	cb = __CFSetGetKeyCallBacks(set);
	if (cb->retain) {
	    newKey = (void *)INVOKE_CALLBACK3(((const void *(*)(CFAllocatorRef, const void *, void *))cb->retain), allocator, key, set->_context);
	} else {
	    newKey = key;
	}
	if (set->_marker == (uintptr_t)newKey || ~set->_marker == (uintptr_t)newKey) {
	    __CFSetFindNewMarker(set);
	}
	CF_OBJC_KVO_WILLCHANGE(set, key);
	CF_WRITE_BARRIER_ASSIGN(keysAllocator, set->_keys[nomatch], newKey);
	set->_count++;
	CF_OBJC_KVO_DIDCHANGE(set, key);
	return newKey;
    }
}

void CFSetReplaceValue(CFMutableSetRef set, const void *key) {
    CFIndex match;
    const CFSetCallBacks *cb;
    const void *newKey;
    CFAllocatorRef allocator;
    CF_OBJC_FUNCDISPATCH1(__kCFSetTypeID, void, set, "_replaceObject:", key);
    __CFGenericValidateType(set, __kCFSetTypeID);
    switch (__CFSetGetType(set)) {
    case __kCFSetMutable:
    case __kCFSetFixedMutable:
	break;
    default:
	CFAssert2(__CFSetGetType(set) != __kCFSetImmutable, __kCFLogAssertion, "%s(): immutable set %p passed to mutating operation", __PRETTY_FUNCTION__, set);
	break;
    }
    if (0 == set->_count) return;
    if (__kCFSetHasNullCallBacks == __CFBitfieldGetValue(((const CFRuntimeBase *)set)->_info, 3, 2)) {
	match = __CFSetFindBuckets1a(set, key);
    } else {
	match = __CFSetFindBuckets1b(set, key);
    }
    if (kCFNotFound == match) return;
    cb = __CFSetGetKeyCallBacks(set);
    allocator = (set->_xflags & __kCFSetWeakKeys) ? kCFAllocatorNull : __CFGetAllocator(set);
    if (cb->retain) {
	newKey = (void *)INVOKE_CALLBACK3(((const void *(*)(CFAllocatorRef, const void *, void *))cb->retain), allocator, key, set->_context);
    } else {
	newKey = key;
    }
    CF_OBJC_KVO_WILLCHANGE(set, key);
    if (cb->release) {
	INVOKE_CALLBACK3(((void (*)(CFAllocatorRef, const void *, void *))cb->release), allocator, set->_keys[match], set->_context);
    }
    CF_WRITE_BARRIER_ASSIGN(allocator, set->_keys[match], newKey);
    CF_OBJC_KVO_DIDCHANGE(set, key);
}

void CFSetSetValue(CFMutableSetRef set, const void *key) {
    CFIndex match, nomatch;
    const CFSetCallBacks *cb;
    const void *newKey;
    CFAllocatorRef allocator;
    CF_OBJC_FUNCDISPATCH1(__kCFSetTypeID, void, set, "_setObject:", key);
    __CFGenericValidateType(set, __kCFSetTypeID);
    switch (__CFSetGetType(set)) {
    case __kCFSetMutable:
	if (set->_count == set->_capacity || NULL == set->_keys) {
	    __CFSetGrow(set, 1);
	}
	break;
    case __kCFSetFixedMutable:
	break;
    default:
	CFAssert2(__CFSetGetType(set) != __kCFSetImmutable, __kCFLogAssertion, "%s(): immutable set %p passed to mutating operation", __PRETTY_FUNCTION__, set);
	break;
    }
    __CFSetFindBuckets2(set, key, &match, &nomatch);
    cb = __CFSetGetKeyCallBacks(set);
    allocator = (set->_xflags & __kCFSetWeakKeys) ? kCFAllocatorNull : __CFGetAllocator(set);
    if (cb->retain) {
	newKey = (void *)INVOKE_CALLBACK3(((const void *(*)(CFAllocatorRef, const void *, void *))cb->retain), allocator, key, set->_context);
    } else {
	newKey = key;
    }
    if (kCFNotFound != match) {
	CF_OBJC_KVO_WILLCHANGE(set, key);
	if (cb->release) {
	    INVOKE_CALLBACK3(((void (*)(CFAllocatorRef, const void *, void *))cb->release), allocator, set->_keys[match], set->_context);
	}
	CF_WRITE_BARRIER_ASSIGN(allocator, set->_keys[match], newKey);
	CF_OBJC_KVO_DIDCHANGE(set, key);
    } else {
	CFAssert3(__kCFSetFixedMutable != __CFSetGetType(set) || set->_count < set->_capacity, __kCFLogAssertion, "%s(): capacity exceeded on fixed-capacity set %p (capacity = %d)", __PRETTY_FUNCTION__, set, set->_capacity);
	if (set->_marker == (uintptr_t)newKey || ~set->_marker == (uintptr_t)newKey) {
	    __CFSetFindNewMarker(set);
	}
	CF_OBJC_KVO_WILLCHANGE(set, key);
	CF_WRITE_BARRIER_ASSIGN(allocator, set->_keys[nomatch], newKey);
	set->_count++;
	CF_OBJC_KVO_DIDCHANGE(set, key);
    }
}

void CFSetRemoveValue(CFMutableSetRef set, const void *key) {
    CFIndex match;
    const CFSetCallBacks *cb;
    CF_OBJC_FUNCDISPATCH1(__kCFSetTypeID, void, set, "removeObject:", key);
    __CFGenericValidateType(set, __kCFSetTypeID);
    switch (__CFSetGetType(set)) {
    case __kCFSetMutable:
    case __kCFSetFixedMutable:
	break;
    default:
	CFAssert2(__CFSetGetType(set) != __kCFSetImmutable, __kCFLogAssertion, "%s(): immutable set %p passed to mutating operation", __PRETTY_FUNCTION__, set);
	break;
    }
    if (0 == set->_count) return;
    if (__kCFSetHasNullCallBacks == __CFBitfieldGetValue(((const CFRuntimeBase *)set)->_info, 3, 2)) {
	match = __CFSetFindBuckets1a(set, key);
    } else {
	match = __CFSetFindBuckets1b(set, key);
    }
    if (kCFNotFound == match) return;
    cb = __CFSetGetKeyCallBacks(set);
    if (1) {
	const void *oldkey = set->_keys[match];
	CF_OBJC_KVO_WILLCHANGE(set, oldkey);
        set->_keys[match] = (const void *)~set->_marker;
	set->_count--;
	CF_OBJC_KVO_DIDCHANGE(set, oldkey);
	if (cb->release) {
	    INVOKE_CALLBACK3(((void (*)(CFAllocatorRef, const void *, void *))cb->release), __CFGetAllocator(set), oldkey, set->_context);
	}
	set->_deletes++;
	if ((__kCFSetMutable == __CFSetGetType(set)) && (set->_bucketsNum < 4 * set->_deletes || (512 < set->_capacity && 3.236067 * set->_count < set->_capacity))) {
	    // 3.236067 == 2 * golden_mean; this comes about because we're trying to resize down
	    // when the count is less than 2 capacities smaller, but not right away when count
	    // is just less than 2 capacities smaller, because an add would then force growth;
	    // well, the ratio between one capacity and the previous is close to the golden
	    // mean currently, so (cap / m / m) would be two smaller; but so we're not close,
	    // we take the average of that and the prior cap (cap / m / m / m). Well, after one
	    // does the algebra, and uses the convenient fact that m^(x+2) = m^(x+1) + m^x if m
	    // is the golden mean, this reduces to cap / 2m for the threshold. In general, the
	    // possible threshold constant is 1 / (2 * m^k), k = 0, 1, 2, ... under this scheme.
	    // Rehash; currently only for mutable-variable sets
	    __CFSetGrow(set, 0);
	} else {
	    // When the probeskip == 1 always and only, a DELETED slot followed by an EMPTY slot
	    // can be converted to an EMPTY slot.  By extension, a chain of DELETED slots followed
	    // by an EMPTY slot can be converted to EMPTY slots, which is what we do here.
	    // _CFSetDecrementValue() below has this same code.
	    if (match < set->_bucketsNum - 1 && set->_keys[match + 1] == (const void *)set->_marker) {
		while (0 <= match && set->_keys[match] == (const void *)~set->_marker) {
		    set->_keys[match] = (const void *)set->_marker;
		    set->_deletes--;
		    match--;
		}
	    }
	}
    }
}

void CFSetRemoveAllValues(CFMutableSetRef set) {
    const void **keys;
    const CFSetCallBacks *cb;
    CFAllocatorRef allocator;
    CFIndex idx, nbuckets;
    CF_OBJC_FUNCDISPATCH0(__kCFSetTypeID, void, set, "removeAllObjects");
    __CFGenericValidateType(set, __kCFSetTypeID);
    switch (__CFSetGetType(set)) {
    case __kCFSetMutable:
    case __kCFSetFixedMutable:
	break;
    default:
	CFAssert2(__CFSetGetType(set) != __kCFSetImmutable, __kCFLogAssertion, "%s(): immutable set %p passed to mutating operation", __PRETTY_FUNCTION__, set);
	break;
    }
    if (0 == set->_count) return;
    keys = set->_keys;
    nbuckets = set->_bucketsNum;
    cb = __CFSetGetKeyCallBacks(set);
    allocator = __CFGetAllocator(set);
    for (idx = 0; idx < nbuckets; idx++) {
	if (set->_marker != (uintptr_t)keys[idx] && ~set->_marker != (uintptr_t)keys[idx]) {
	    const void *oldkey = keys[idx];
	    CF_OBJC_KVO_WILLCHANGE(set, oldkey);
	    keys[idx] = (const void *)~set->_marker;
	    set->_count--;
	    CF_OBJC_KVO_DIDCHANGE(set, oldkey);
	    if (cb->release) {
		INVOKE_CALLBACK3(((void (*)(CFAllocatorRef, const void *, void *))cb->release), allocator, oldkey, set->_context);
	    }
	}
    }
    // XXX need memset here
    for (idx = 0; idx < nbuckets; idx++) {
	keys[idx] = (const void *)set->_marker;
    }
    set->_count = 0;
    set->_deletes = 0;
    if ((__kCFSetMutable == __CFSetGetType(set)) && (512 < set->_capacity)) {
	__CFSetGrow(set, 256);
    }
}