#include "Block_private.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define __USE_GNU
#include <dlfcn.h>
#if __has_include(<os/assumes.h>)
#include <os/assumes.h>
#else
#include <assert.h>
#endif
#ifndef os_assumes
#define os_assumes(_x) _x
#endif
#ifndef os_assert
#define os_assert(_x) assert(_x)
#endif
#if TARGET_OS_WIN32
#define _CRT_SECURE_NO_WARNINGS 1
#include <windows.h>
static __inline bool OSAtomicCompareAndSwapLong(long oldl, long newl, long volatile *dst)
{
long original = InterlockedCompareExchange(dst, newl, oldl);
return (original == oldl);
}
static __inline bool OSAtomicCompareAndSwapInt(int oldi, int newi, int volatile *dst)
{
int original = InterlockedCompareExchange(dst, newi, oldi);
return (original == oldi);
}
#else
#define OSAtomicCompareAndSwapLong(_Old, _New, _Ptr) __sync_bool_compare_and_swap(_Ptr, _Old, _New)
#define OSAtomicCompareAndSwapInt(_Old, _New, _Ptr) __sync_bool_compare_and_swap(_Ptr, _Old, _New)
#endif
static void *_Block_copy_class = _NSConcreteMallocBlock;
static void *_Block_copy_finalizing_class = _NSConcreteMallocBlock;
static int _Block_copy_flag = BLOCK_NEEDS_FREE;
static int _Byref_flag_initial_value = BLOCK_BYREF_NEEDS_FREE | 4;
static bool isGC = false;
static int32_t latching_incr_int(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return BLOCK_REFCOUNT_MASK;
}
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
return old_value+2;
}
}
}
static bool latching_incr_int_not_deallocating(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if (old_value & BLOCK_DEALLOCATING) {
return false;
}
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return true;
}
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
return true;
}
}
}
static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return false; }
if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
return false; }
int32_t new_value = old_value - 2;
bool result = false;
if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {
new_value = old_value - 1;
result = true;
}
if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
return result;
}
}
}
static bool latching_decr_int_now_zero(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return false; }
if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
return false; }
int32_t new_value = old_value - 2;
if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
return (new_value & BLOCK_REFCOUNT_MASK) == 0;
}
}
}
#if !TARGET_OS_WIN32
#pragma mark GC Support Routines
#endif
static void *_Block_alloc_default(const unsigned long size, const bool initialCountIsOne, const bool isObject) {
return malloc(size);
}
static void _Block_assign_default(void *value, void **destptr) {
*destptr = value;
}
static void _Block_setHasRefcount_default(const void *ptr, const bool hasRefcount) {
}
static void _Block_do_nothing(const void *aBlock) { }
static void _Block_retain_object_default(const void *ptr) {
}
static void _Block_release_object_default(const void *ptr) {
}
static void _Block_assign_weak_default(const void *ptr, void *dest) {
#if !TARGET_OS_WIN32
*(long *)dest = (long)ptr;
#else
*(void **)dest = (void *)ptr;
#endif
}
static void _Block_memmove_default(void *dst, void *src, unsigned long size) {
memmove(dst, src, (size_t)size);
}
static void _Block_memmove_gc_broken(void *dest, void *src, unsigned long size) {
void **destp = (void **)dest;
void **srcp = (void **)src;
while (size) {
_Block_assign_default(*srcp, destp);
destp++;
srcp++;
size -= sizeof(void *);
}
}
static void _Block_destructInstance_default(const void *aBlock) {}
static void *(*_Block_allocator)(const unsigned long, const bool isOne, const bool isObject) = _Block_alloc_default;
static void (*_Block_deallocator)(const void *) = (void (*)(const void *))free;
static void (*_Block_assign)(void *value, void **destptr) = _Block_assign_default;
static void (*_Block_setHasRefcount)(const void *ptr, const bool hasRefcount) = _Block_setHasRefcount_default;
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
static void (*_Block_release_object)(const void *ptr) = _Block_release_object_default;
static void (*_Block_assign_weak)(const void *dest, void *ptr) = _Block_assign_weak_default;
static void (*_Block_memmove)(void *dest, void *src, unsigned long size) = _Block_memmove_default;
static void (*_Block_destructInstance) (const void *aBlock) = _Block_destructInstance_default;
void _Block_use_GC( void *(*alloc)(const unsigned long, const bool isOne, const bool isObject),
void (*setHasRefcount)(const void *, const bool),
void (*gc_assign)(void *, void **),
void (*gc_assign_weak)(const void *, void *),
void (*gc_memmove)(void *, void *, unsigned long)) {
isGC = true;
_Block_allocator = alloc;
_Block_deallocator = _Block_do_nothing;
_Block_assign = gc_assign;
_Block_copy_flag = BLOCK_IS_GC;
_Block_copy_class = _NSConcreteAutoBlock;
_Block_copy_finalizing_class = _NSConcreteFinalizingBlock;
_Block_setHasRefcount = setHasRefcount;
_Byref_flag_initial_value = BLOCK_BYREF_IS_GC; _Block_retain_object = _Block_do_nothing;
_Block_release_object = _Block_do_nothing;
_Block_assign_weak = gc_assign_weak;
_Block_memmove = gc_memmove;
}
void _Block_use_GC5( void *(*alloc)(const unsigned long, const bool isOne, const bool isObject),
void (*setHasRefcount)(const void *, const bool),
void (*gc_assign)(void *, void **),
void (*gc_assign_weak)(const void *, void *)) {
_Block_use_GC(alloc, setHasRefcount, gc_assign, gc_assign_weak, _Block_memmove_gc_broken);
}
BLOCK_EXPORT
void _Block_use_RR( void (*retain)(const void *),
void (*release)(const void *)) {
_Block_retain_object = retain;
_Block_release_object = release;
_Block_destructInstance = dlsym(RTLD_DEFAULT, "objc_destructInstance");
}
BLOCK_EXPORT
void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
_Block_retain_object = callbacks->retain;
_Block_release_object = callbacks->release;
_Block_destructInstance = callbacks->destructInstance;
}
#if 0
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;
}
#endif
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
static __inline bool _Block_has_layout(struct Block_layout *aBlock) {
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return false;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return ((struct Block_descriptor_3 *)desc)->layout != NULL;
}
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->copy)(result, aBlock); }
static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->dispose)(aBlock);
}
#if !TARGET_OS_WIN32
#pragma mark Copy/Release support
#endif
static void *_Block_copy_internal(const void *arg, const bool wantsOne) {
struct Block_layout *aBlock;
if (!arg) return NULL;
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GC) {
if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 2)) {
_Block_setHasRefcount(aBlock, true);
}
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
if (!isGC) {
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); result->flags |= BLOCK_NEEDS_FREE | 2; result->isa = _NSConcreteMallocBlock;
_Block_call_copy_helper(result, aBlock);
return result;
}
else {
int32_t flags = aBlock->flags;
bool hasCTOR = (flags & BLOCK_HAS_CTOR) != 0;
struct Block_layout *result = _Block_allocator(aBlock->descriptor->size, wantsOne, hasCTOR || _Block_has_layout(aBlock));
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); flags &= ~(BLOCK_NEEDS_FREE|BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); if (wantsOne)
flags |= BLOCK_IS_GC | 2;
else
flags |= BLOCK_IS_GC;
result->flags = flags;
_Block_call_copy_helper(result, aBlock);
if (hasCTOR) {
result->isa = _NSConcreteFinalizingBlock;
}
else {
result->isa = _NSConcreteAutoBlock;
}
return result;
}
}
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;
if (src->forwarding->flags & BLOCK_BYREF_IS_GC) {
; }
else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; copy->forwarding = copy; src->forwarding = copy; copy->size = src->size;
if (isWeak) {
copy->isa = &_NSConcreteWeakBlockVariable; }
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
(*src2->byref_keep)(copy, src);
}
else {
_Block_memmove(copy+1, src+1,
src->size - sizeof(struct Block_byref));
}
}
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
_Block_assign(src->forwarding, (void **)destp);
}
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
int32_t refcount;
byref = byref->forwarding;
if ((byref->flags & BLOCK_BYREF_NEEDS_FREE) == 0) {
return; }
refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);
}
_Block_deallocator((struct Block_layout *)byref);
}
}
#if !TARGET_OS_WIN32
#pragma mark SPI/API
#endif
BLOCK_EXPORT
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, true);
}
BLOCK_EXPORT
void _Block_release(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock
|| (aBlock->flags & BLOCK_IS_GLOBAL)
|| ((aBlock->flags & (BLOCK_IS_GC|BLOCK_NEEDS_FREE)) == 0)
) return;
if (aBlock->flags & BLOCK_IS_GC) {
if (latching_decr_int_now_zero(&aBlock->flags)) {
_Block_setHasRefcount(aBlock, false);
}
}
else if (aBlock->flags & BLOCK_NEEDS_FREE) {
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
_Block_call_dispose_helper(aBlock);
_Block_destructInstance(aBlock);
_Block_deallocator(aBlock);
}
}
}
BLOCK_EXPORT
bool _Block_tryRetain(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
return latching_incr_int_not_deallocating(&aBlock->flags);
}
BLOCK_EXPORT
bool _Block_isDeallocating(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
return (aBlock->flags & BLOCK_DEALLOCATING) != 0;
}
static void _Block_destroy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return;
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_IS_GC) {
return; }
_Block_release(aBlock);
}
BLOCK_EXPORT
void *_Block_copy_collectable(const void *aBlock) {
return _Block_copy_internal(aBlock, false);
}
BLOCK_EXPORT
size_t Block_size(void *aBlock) {
return ((struct Block_layout *)aBlock)->descriptor->size;
}
BLOCK_EXPORT
bool _Block_use_stret(void *aBlock) {
struct Block_layout *layout = (struct Block_layout *)aBlock;
int requiredFlags = BLOCK_HAS_SIGNATURE | BLOCK_USE_STRET;
return (layout->flags & requiredFlags) == requiredFlags;
}
BLOCK_EXPORT
bool _Block_has_signature(void *aBlock) {
return _Block_signature(aBlock) ? true : false;
}
BLOCK_EXPORT
const char * _Block_signature(void *aBlock)
{
struct Block_descriptor_3 *desc3 = _Block_descriptor_3(aBlock);
if (!desc3) return NULL;
return desc3->signature;
}
BLOCK_EXPORT
const char * _Block_layout(void *aBlock)
{
struct Block_layout *layout = (struct Block_layout *)aBlock;
if (layout->flags & BLOCK_HAS_EXTENDED_LAYOUT) return NULL;
struct Block_descriptor_3 *desc3 = _Block_descriptor_3(aBlock);
if (!desc3) return NULL;
return desc3->layout;
}
BLOCK_EXPORT
const char * _Block_extended_layout(void *aBlock)
{
struct Block_layout *layout = (struct Block_layout *)aBlock;
if (! (layout->flags & BLOCK_HAS_EXTENDED_LAYOUT)) return NULL;
struct Block_descriptor_3 *desc3 = _Block_descriptor_3(aBlock);
if (!desc3) return NULL;
if (!desc3->layout) return "";
else return desc3->layout;
}
#if !TARGET_OS_WIN32
#pragma mark Compiler SPI entry points
#endif
BLOCK_EXPORT
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
_Block_retain_object(object);
_Block_assign((void *)object, destAddr);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_assign(_Block_copy_internal(object, false), destAddr);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
_Block_byref_assign_copy(destAddr, object, flags);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
_Block_assign((void *)object, destAddr);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
_Block_assign_weak(object, destAddr);
break;
default:
break;
}
}
BLOCK_EXPORT
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_destroy(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}