/* * Copyright (c) 2009 Remy Demarest * Portions Copyright (c) 2009 David Chisnall * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #import "objc/blocks_runtime.h" #import "objc/runtime.h" #include #include #include #include /* Makes the compiler happy even without Foundation */ @interface Dummy - (id)retain; - (void)release; @end // Descriptor attributes enum { BLOCK_HAS_COPY_DISPOSE = (1 << 25), BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code BLOCK_IS_GLOBAL = (1 << 28), /** * Block function uses a calling convention that returns a structure via a * pointer passed in by the caller. */ BLOCK_USE_SRET = (1 << 29), /** * Block has an Objective-C type encoding. */ BLOCK_HAS_SIGNATURE = (1 << 30) }; // _Block_object_assign() and _Block_object_dispose() flag helpers. enum { BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ... BLOCK_FIELD_IS_BLOCK = 7, // a block variable BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable BLOCK_FIELD_IS_WEAK = 16, // declared __weak BLOCK_BYREF_CALLER = 128, // called from byref copy/dispose helpers }; /** * Block descriptor that contains copy and dispose operations. */ struct block_descriptor_copydispose { unsigned long int reserved; /** Size of the block. */ unsigned long int size; /** * Copy function, generated by the compiler to help copy the block if it * contains nontrivial copy operations. */ void (*copy_helper)(void *dst, void *src); /** * Dispose function, generated by the compiler to help copy the block if it * contains nontrivial destructors. */ void (*dispose_helper)(void *src); /** * Objective-C type encoding of the block. */ const char *encoding; }; /** * Block descriptor that does not contain copy and dispose helper functions. */ struct block_descriptor { unsigned long int reserved; /** Size of the block. */ unsigned long int size; /** * Objective-C type encoding of the block. */ const char *encoding; }; // Helper structure struct psy_block_literal { void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags; int reserved; void (*invoke)(void *, ...); struct block_descriptor_copydispose *descriptor; }; // Helper structure struct psy_block_byref_obj { void *isa; // uninitialized struct psy_block_byref_obj *forwarding; int flags; //refcount; int size; void (*byref_keep)(struct psy_block_byref_obj *dst, const struct psy_block_byref_obj *src); void (*byref_dispose)(struct psy_block_byref_obj *); }; const char *block_getType_np(void *b) { struct psy_block_literal *block = b; if ((NULL == block) || !(block->flags & BLOCK_HAS_SIGNATURE)) { return NULL; } if (!(block->flags & BLOCK_HAS_COPY_DISPOSE)) { return ((struct block_descriptor*)block->descriptor)->encoding; } return block->descriptor->encoding; } /* Certain field types require runtime assistance when being copied to the * heap. The following function is used to copy fields of types: blocks, * pointers to byref structures, and objects (including * __attribute__((NSObject)) pointers. BLOCK_FIELD_IS_WEAK is orthogonal to * the other choices which are mutually exclusive. Only in a Block copy helper * will one see BLOCK_FIELD_IS_BYREF. */ void _Block_object_assign(void *destAddr, const void *object, const int flags) { //printf("Copying %x to %x with flags %x\n", object, destAddr, flags); // FIXME: Needs to be implemented if(flags & BLOCK_FIELD_IS_WEAK) { } else { if(flags & BLOCK_FIELD_IS_BYREF) { const struct psy_block_byref_obj *src = object; struct psy_block_byref_obj **dst = destAddr; /* I followed Apple's specs saying byref's "flags" field should * represent the refcount but it still contains real flag, so this * is a little hack... */ if((src->flags & ~BLOCK_HAS_COPY_DISPOSE) == 0) { *dst = malloc(src->size); memcpy(*dst, src, src->size); if (src->forwarding == src) { (*dst)->forwarding = *dst; } if((size_t)src->size >= sizeof(struct psy_block_byref_obj)) { src->byref_keep(*dst, src); } } else *dst = (struct psy_block_byref_obj*)src; (*dst)->flags++; } else if((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) { struct psy_block_literal *src = (struct psy_block_literal*)object; struct psy_block_literal **dst = destAddr; *dst = Block_copy(src); } else if((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) { id src = (id)object; id *dst = destAddr; *dst = [src retain]; } } } /* Similarly a compiler generated dispose helper needs to call back for each * field of the byref data structure. (Currently the implementation only packs * one field into the byref structure but in principle there could be more). * The same flags used in the copy helper should be used for each call * generated to this function: */ void _Block_object_dispose(const void *object, const int flags) { // FIXME: Needs to be implemented if(flags & BLOCK_FIELD_IS_WEAK) { } else { if(flags & BLOCK_FIELD_IS_BYREF) { struct psy_block_byref_obj *src = (struct psy_block_byref_obj*)object; src->flags--; if((src->flags & ~BLOCK_HAS_COPY_DISPOSE) == 0) { if((size_t)src->size >= sizeof(struct psy_block_byref_obj)) src->byref_dispose(src); free(src); } } else if((flags & ~BLOCK_BYREF_CALLER) == BLOCK_FIELD_IS_BLOCK) { struct psy_block_literal *src = (struct psy_block_literal*)object; Block_release(src); } else if((flags & ~BLOCK_BYREF_CALLER) == BLOCK_FIELD_IS_OBJECT) { id src = (id)object; [src release]; } } } struct StackBlockClass { void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags; int reserved; void (*invoke)(void *, ...); struct { unsigned long int reserved; // NULL unsigned long int size; // sizeof(struct Block_literal_1) // optional helper functions void (*copy_helper)(void *dst, void *src); void (*dispose_helper)(void *src); } *descriptor; const char *types; }; // Copy a block to the heap if it's still on the stack or increments its retain count. void *_Block_copy(void *src) { struct StackBlockClass *self = src; struct StackBlockClass *ret = self; extern void _NSConcreteStackBlock __attribute__((weak)); // If the block is Global, there's no need to copy it on the heap. if(self->isa == &_NSConcreteStackBlock) { if(self->reserved == 0) { ret = malloc(self->descriptor->size); memcpy(ret, self, self->descriptor->size); if(self->flags & BLOCK_HAS_COPY_DISPOSE) { self->descriptor->copy_helper(ret, self); } memcpy(self, ret, self->descriptor->size); } ret->reserved++; } return ret; } // Release a block and frees the memory when the retain count hits zero. void _Block_release(void *src) { struct StackBlockClass *self = src; extern void _NSConcreteStackBlock __attribute__((weak)); if(self->isa == &_NSConcreteStackBlock && // A Global block doesn't need to be released self->reserved > 0) // If false, then it's not allocated on the heap, we won't release auto memory ! { self->reserved--; if(self->reserved == 0) { if(self->flags & BLOCK_HAS_COPY_DISPOSE) self->descriptor->dispose_helper(self); free(self); } } }