Home > Articles > Home & Office Computing > Mac OS X

  • Print
  • + Share This
This chapter is from the book

For the More Curious: Blocks Internals

Blocks and __block variables are implemented by a combination of structures and functions. These are generated by the compiler and maintained by a runtime environment. Knowing how things work under the hood is useful for understanding the details of memory management and debugging.

Implementation

The compiler interprets the new, block-specific syntax and generates data to interface with the runtime and code that relies on functions provided by the runtime. The blocks runtime enables the use of blocks while the application is actually running.

The specific compiler is irrelevant to our discussion. The most visible difference between gcc and clang is in the names generated for the private structures and functions created by the compiler to support blocks and __block variables.

Those private structures and functions make up the heart of the blocks implementation.

Block literals

Each block literal definition triggers the compiler to generate two structures and at least one function. The two structures describe the block and its runtime information. The function contains the executable code of the block.

The two structures are the block literal (also known as the "block holder") and the block descriptor.

A block descriptor looks like:

static const struct block_descriptor_NAME {
    unsigned long reserved;
    unsigned long literal_size;

    /* helper functions - present only if needed */
    void (*copy_helper)(void *dst, void *src);
    void (*dispose_helper)(void *src);
};

The reserved field is currently unused. The literal_size field is set to the size of the corresponding block literal. The two helper function pointers are only present if needed. They are needed when the block references an Objective-C or C++ object or a __block variable. When helper functions are necessary, the compiler generates them in addition to the function implementing the body of the block literal.

A block literal looks like:

struct block_literal_NAME {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *literal, ...);
    struct block_descriptor_NAME *descriptor;
    /* referenced captured variables follow */
};

The isa pointer is what makes a block into an Objective-C object. Even when not using Objective-C, the isa pointer is still used by the blocks runtime to indicate what kind of block it is dealing with.

The isa field will point to:

_NSConcreteStackBlock

when the block is on the stack.

_NSConcreteGlobalBlock

when the block is in global storage.

_NSConcreteMallocBlock

when the block is on the heap.

_NSConcreteAutoBlock

when the block is in collectable memory. This class is used when running under the garbage collector and a stack block not referencing a C++ object is copied to the heap.

_NSConcreteFinalizingBlock

when the block is in collectable memory and must have a finalizer run when it is collected. This class is used when running under the garbage collector and a stack block referencing a C++ object is copied to the heap, because the runtime must ensure that the C++ object's destructor is called when the block is collected.

All of these block classes are subclasses of _NSAbstractBlock. The abstract class provides implementations for the memory-related methods used by blocks. The various concrete subclasses exist solely to indicate information about where the block is stored.

The flags field provides further information about the block:

enum {
    BLOCK_REFCOUNT_MASK    = (0xFFFF),
    BLOCK_NEEDS_FREE       = (1 << 24),
    BLOCK_HAS_COPY_DISPOSE = (1 << 25),
    BLOCK_HAS_CXX_OBJ      = (1 << 26),
    BLOCK_IS_GC            = (1 << 27),
    BLOCK_IS_GLOBAL        = (1 << 28),
    BLOCK_HAS_DESCRIPTOR   = (1 << 29),
};

BLOCK_REFCOUNT_MASK, BLOCK_NEEDS_FREE, and BLOCK_IS_GC are set as appropriate by the runtime when a block is copied.

BLOCK_IS_GLOBAL is set at compile time for blocks in global storage. Copying and releasing such a block has no effect, as the block is always present in the application's memory. The compiler might opt to hoist a stack-local block into static memory and set the BLOCK_IS_GLOBAL flag if it has no references to any stack-local (which includes __block) variables.

BLOCK_HAS_DESCRIPTOR is always set. It was added to distinguish the version of the blocks implementation that was eventually released with Snow Leopard from an earlier implementation.

Every block invoke function takes a pointer to the calling block literal as its first argument. This provides the function with access to the block's captured variables. This is functionally identical to the this pointer passed as the first argument to C++ member functions, which the member function uses to access the member variables, and the self pointer supplied as the first argument to Objective-C instance methods, which the method uses to access the instance variables. As in C++, the return value and remaining arguments of the block invoke function are those declared by the programmer. (Objective-C adds one more implicit argument between self and the programmer-declared arguments, _cmd, which is set to the method's selector.)

Aside from the obvious referenced captured variables, blocks also are considered to have referenced all variables referenced by any blocks nested within them. Consider this brief example:

int x = 0;
int y = 1;
int (^b)(void) = ^{
    int (^c)(void) = ^{
        return y;
    };
    return x + c();
}

Here, the block assigned to b is considered to have referenced both x and y.

To see how block literals, block descriptors, and block invoke functions come together, consider this code:

void f(void) {
    int x = 0;
    int (^b)(void) = ^{ return x + 1; };
    int y = b();
}

The compiler would turn that code into something like this:

typedef void (*generic_invoke_funcptr)(void *, ...);
struct __block_literal {
    void *isa;
    int flags;
    int reserved;
    generic_invoke_funcptr invoke;
    struct __block_descriptor_tmp *descriptor;
    const int captured_x;
};

static const struct __block_descriptor_tmp {
    unsigned long reserved;
    unsigned long literal_size;
    /* no copy/dispose helpers needed */
} __block_descriptor_tmp = {
    0UL, sizeof(struct __block_literal)
};

// ^int (void) { return x + 1; }
int __f_block_invoke_(struct __block_literal *bp) {
    return bp->captured_x + 1;
}
typedef int (*iv_funcptr)(struct __block_literal *);

void f(void) {
    int x = 0;
    // int (^b)(void) = ^{ return x + 1 };
    struct __block_literal __b = {
        .isa = &_NSConcreteStackBlock,
        .flags = BLOCK_HAS_DESCRIPTOR,
        .reserved = 0,
        .invoke = (generic_invoke_funcptr)__f_block_invoke_,
        .descriptor = &__block_descriptor_tmp,
        .captured_x = x
    };
    struct __block_literal *b = &__b;
    int y = (*(iv_funcptr)(b->invoke))(b);
}

Notice that the block variable is really a pointer to a structure created on the stack. It can be helpful to keep this in mind when thinking about when a block literal must be copied or not.

__block variables

Like block literals, __block variables can move from the stack to the heap and their variable data can require memory management, such as when it is an Objective-C object. Consequently, __block variables are also compiled into a struct and, if necessary, ancillary functions.

In order that all manipulations of the __block variable deal with the current location of the variable, all access is mediated by a forwarding pointer. When a __block variable is copied from the stack to the heap, the forwarding pointers of both the on-stack and in-heap structures are updated to point to the in-heap structure.

Because all __block variable access is by reference, the names of the structure and functions associated with __block variables embed "byref."

The byref structure looks like:

struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    int flags;
    int size;

    /* helper functions - present only if needed */
    void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
    void (*byref_destroy)(struct Block_byref *);

    /* actual variable data follows */
}

The isa field is always NULL to start with. When a __weak-qualified __block variable is copied, the field is set to &NSConcreteWeakBlockVariable.

The forwarding pointer always points to the start of the authoritative byref header. To begin with, this will always be the address of the containing byref structure itself.

The flags field is used to indicate whether copy and dispose helper functions are present. If they are not, it will be initialized to 0; otherwise, it will be initialized to BLOCK_HAS_COPY_DISPOSE. As with block literals, when the structure is copied at runtime, the flags field will be updated with memory management information. If it is copied into scanned memory, BLOCK_IS_GC will be set. Otherwise, BLOCK_NEEDS_FREE will be set and the bottom two bytes used to store a reference count.

The size is set to the size of the particular Block_byref structure.

Helper functions will be synthesized by the compiler if the byref variable is a block reference, an Objective-C object, or a C++ object. If they are present, flags will include BLOCK_HAS_COPY_DISPOSE. They will be invoked when copying and when releasing a block that references a __block variable.

When a block captures a __block variable, it holds onto the byref structure's forwarding pointer and uses that to interact with the variable. The block will then need copy and dispose helpers to handle copying and disposing of the captured __block variable.

As an example, we will return to the function f. We will change it slightly by moving the referenced variable x from auto storage to __block storage:

void f(void) {
    __block int x = 0;
    int (^b)(void) = ^{ return x + 1; };
    int y = b();
}

In response, the compiler will generate something like the following code (the changes wrought by adding __block have been emphasized):

   // __block int x
   struct __byref_x {
   /* header */
   void *isa;
   struct __byref_x *forwarding;
   int flags;
   int size;
   /* no helpers needed */
   int x;
   };


typedef void (*generic_invoke_funcptr)(void *, ...);
struct __block_literal {
    void *isa;
    int flags;
    int reserved;
    generic_invoke_funcptr invoke;
    struct __block_descriptor_tmp *descriptor;

    struct __byref_x *captured_x;

};


void __copy_helper_block_(struct __block_literal *dst,
                             struct __block_literal *src);
void __destroy_helper_block_(struct __block_literal *bp);


typedef void (*generic_copy_funcptr)(void *, void *);
typedef void (*generic_dispose_funcptr)(void *);
static const struct __block_descriptor_tmp {
    unsigned long reserved;
    unsigned long literal_size;

    /* helpers to copy __block reference captured_x */
    generic_copy_funcptr copy;
    generic_dispose_funcptr dispose;

} __block_descriptor_tmp = {
    0UL, sizeof(struct __block_literal),

   (generic_copy_funcptr)__copy_helper_block_,
   (generic_dispose_funcptr)__destroy_helper_block_
};

// ^int (void) { return x + 1; }
int __f_block_invoke_(struct __block_literal *bp) {
    return bp->captured_x->forwarding->x + 1;
}
typedef int (*iv_funcptr)(struct __block_literal *);


void f(void) {

    // __block int x = 0;
    struct __byref_x x = {
        .isa = NULL,
        .forwarding = &x,
        .flags = 0,
        .size = sizeof(x),
        .x = 0
    };

    // int (^b)(void) = ^{ return x + 1 };
    struct __block_literal __b = {
        .isa = &_NSConcreteStackBlock,
        .flags = BLOCK_HAS_DESCRIPTOR,
        .reserved = 0,
        .invoke = (generic_invoke_funcptr)__f_block_invoke_,
        .descriptor = &__block_descriptor_tmp,

        .captured_x = x.forwarding

    };
    struct __block_literal *b = &__b;
    int y = (*(iv_funcptr)(b->invoke))(b);


    // Clean up before leaving scope of x.
    _Block_object_dispose(x.forwarding, BLOCK_FIELD_IS_BYREF);
}


void __copy_helper_block_(struct __block_literal *dst,
                          struct __block_literal *src) {
  _Block_object_assign(&dst->captured_x, src->captured_x,
                       BLOCK_FIELD_IS_BYREF);
}

void __destroy_helper_block_(struct __block_literal *bp) {
  _Block_object_dispose(bp->captured_x, BLOCK_FIELD_IS_BYREF);
}

Of particular note here is the call to _Block_object_dispose() at the end of f(). This is because, when garbage collection is not being used, the runtime must adjust the reference count of the __block variable whenever it goes out of scope. When all references have been eliminated, the runtime releases any allocated storage.

The functions used by the helper functions, _Block_object_assign() and _Block_object_dispose(), are provided by the blocks runtime for use by the compiler. Their behavior is heavily determined by the final argument, const int flags, which provides information on the type of the object being assigned or disposed of. The possible values of this field are:

enum {
    BLOCK_FIELD_IS_OBJECT   =  3,
    BLOCK_FIELD_IS_BLOCK    =  7,
    BLOCK_FIELD_IS_BYREF    =  8,
    BLOCK_FIELD_IS_WEAK     = 16,
    BLOCK_BYREF_CALLER      = 128
};

The BLOCK_BYREF_CALLER flag is used to signal to the functions that they are being called by a byref structure's byref_keep or byref_destroy function. It is only ever set by such functions.

The other flags are set as appropriate for the type of the object being assigned or disposed. Notice that a block field is also an object, since BLOCK_FIELD_IS_BLOCK & BLOCK_FIELD_IS_OBJECT results in BLOCK_FIELD_IS_OBJECT. Where distinguishing between an object and a block object is important, the runtime functions are careful to test whether the block flag is set before testing whether the object flag is set.

Debugging

Debugging blocks can be tricky as the debugging environment straddles the line between the abstraction and the implementation. gcc provides far better debugging information than clang, but this might change in the future.

gdb comes with only one block-specific command: invoke-block, which you can unambiguously abbreviate to inv. Its arguments are a block reference or the address of a block literal structure followed by the declared arguments to the block function. The arguments are separated by spaces, so arguments with spaces must be enclosed in double quotation marks. Double quotation marks within quoted arguments must be escaped with a backslash. The only time you are likely to encounter this is in passing a string argument to a block; the resulting command would look like:

inv string_block "\"string argument\""

gcc and clang differ significantly in the debugging information they supply for blocks.

gcc's debugging information

gcc embeds a goodly amount of debugging information about blocks. The print command (p for short) picks up that block references are pointers, and you can use ptype to print the compiler-generated type of a block:

(gdb) p local_block
$1 = (struct __block_literal_2 *) 0xbffff854
(gdb) ptype local_block
type = struct __block_literal_2 {
    void *__isa;
    int __flags;
    int __reserved;
    void *__FuncPtr;
    struct __block_descriptor_withcopydispose *__descriptor;
    const char *enc_vbv;
    struct __Block_byref_1_i *i;
} *
(gdb) ptype local_block->__descriptor
type = struct __block_descriptor_withcopydispose {
    long unsigned int reserved;
    long unsigned int Size;
    void *CopyFuncPtr;
    void *DestroyFuncPtr;
} *

You can also use the Objective-C command print-object (po for short) to get a different view on the block:

(gdb) po local_block
<__NSStackBlock__: 0xbffff854>

Getting information on local variables within a block will show that gcc adds the __func__ variable, which is set to the name of the function. If you get information on the function arguments, you will see the implicit block literal pointer argument:

(gdb) i args
.block_descriptor = (struct __block_literal_2 *) 0xbffff854

The debugging information generated by gcc pretends that __block variables are identical to their auto counterparts, so that if you have a variable __block int i, you will find that printing the i and its size will behave the same as printing a variable int i.

clang's debugging information

clang, unfortunately, provides no debugging information for block references. The debugger finds no type information for block references. It also has no way to look up the block implementation function for a block so that invoke-block always fails.

You can still set breakpoints in blocks by setting them at a line in a file or at the invocation function, if you can determine its name, but you will find that clang pretends that block implementation functions have the same arguments as the block literal, so you cannot readily gain access to the implicit block literal pointer argument. Interestingly, clang does not report any __func__ local variable; it generates a warning if you use it from a block literal, but you will find that the variable is in fact present, regardless of what the debugging information says.

clang also emits no debugging information for __block variables. They do not appear in the list of local variables, and any attempt to reference them results in a message like:

No symbol "i" in current context.

While you could make headway by using what you know of the blocks implementation to cadge the desired information out of a program compiled using clang, until these issues are fixed, you would do well to use gcc when compiling an application using blocks where you plan to rely on the debugging information in future.

Dumping runtime information

Apple's blocks runtime includes a couple functions for dumping information about a block reference and a __block variable.

These functions are:

const char *_Block_dump(const void *block);
const char *_Block_byref_dump(struct Block_byref *src);

You can call these from gdb to dump information about a block or __block variable. If you have the following declarations:

__block int i = 23;
void (^local_block)(void) = ^{ /*...*/ };

then you can dump information about them as follows:

(gdb) call (void)printf((const char *)_Block_dump(local_block))
^0xbffff854 (new layout) =
isa: stack Block
flags: HASDESCRIPTOR HASHELP
refcount: 0
invoke: 0x1e50
descriptor: 0x20bc
descriptor->reserved: 0
descriptor->size: 28
descriptor->copy helper: 0x1e28
descriptor->dispose helper: 0x1e0a

(gdb) set $addr = (char *)&i - 2*sizeof(int) - 2*sizeof(void *)
(gdb) call (void)printf((const char *)_Block_byref_dump($addr))
byref data block 0xbffff870 contents:
  forwarding: 0xbffff870
  flags: 0x0
  size: 20

Note that, though the debugging information supplied by gcc pretends that the __block variable i is simply an int variable, the address of the variable is in fact its address within the byref structure. Since we know the layout of the structure, we can calculate the address of the start of the structure and pass that to _Block_byref_dump().

You can wrap these calls in user-defined commands. Adding the following definitions to your .gdbinit file will make them available whenever you run gdb:

define dump-block-literal
    printf "%s", (const char *)_Block_dump($arg0)
end

document dump-block-literal
    Dumps runtime information about the supplied block reference.
    Argument is the name or address of a block reference.
end


define dump-block-byref
    set $_dbb_addr = (char *)&$arg0 - 2*sizeof(int) - 2*sizeof(void *)
    printf "%s", (const char *)_Block_byref_dump($_dbb_addr)
end

document dump-block-byref
    Dumps runtime information about the supplied __block variable.
    Argument is a pointer to the variable embedded in a block byref structure.
end

With these commands defined, dumping that information is as simple as:

(gdb) dump-block-literal local_block
^0xbffff854 (new layout) =
isa: stack Block
flags: HASDESCRIPTOR HASHELP
refcount: 0
invoke: 0x1e50
descriptor: 0x20bc
descriptor->reserved: 0
descriptor->size: 28
descriptor->copy helper: 0x1e28
descriptor->dispose helper: 0x1e0a
(gdb) dump-block-byref i
byref data block 0xbffff870 contents:
  forwarding: 0xbffff870
  flags: 0x0
  size: 20

Evolving the implementation

This chapter has described the blocks runtime as released with Mac OS X 10.6 (Snow Leopard). The blocks runtime is unlikely to make any changes that would break code compiled to that interface, but it will not stop evolving.

There are several extension points built into the current runtime. The various flags fields can be carefully extended; reserved fields can be repurposed; and new fields can be tacked on at the ends of the various structures.

One minor extension that might see release is the addition of a signature field at the end of the block descriptor structure. This field would contain a pointer to the Objective-C type encoding of the block invoke function.

(For those curious, blocks themselves are encoded by the @encode directive as @?; this parallels the function pointer encoding of ^?, which literally reads as "pointer to unknown type.")

To indicate that this field is present, the BLOCK_HAS_DESCRIPTOR flag would no longer be set, and a new flag, BLOCK_HAS_SIGNATURE = (1 << 30), would be set in all blocks compiled for the new runtime.

With the ability to test for BLOCK_HAS_SIGNATURE to check for a block compiled against a newer version of the runtime, the way is opened for other changes, including repurposing BLOCK_HAS_DESCRIPTOR to signal that a block returns a structure large enough to require special handling on some architectures. The flag could be renamed BLOCK_USE_STRET. This is similar to the way that objc_msgSend_stret() is used by the Objective-C runtime instead of objc_msgSend() in the same situation.

A more significant change would be to add the signature of the variables captured by a block. This would allow the runtime to eliminate helper functions in favor of using the type information now encoded along with the block to itself do the right thing for all captured variables when a block is copied or disposed.

Compiler-generated names

Both gcc and clang automatically generate names for the structures and functions that make up blocks and __block variables. Sometimes during debugging, it would be useful to be able to guess the names generated in implementing a given block or __block variable.

Unfortunately, outside of toy examples, this is generally not possible without reference to a disassembly of the code. Both compilers disambiguate the names of the structures and helper functions they generate by appending numbers to the end of the same string. Thus, you can judge the type of support structure or helper you are looking at, but you cannot readily trace it back to the block or __block variable that caused its generation.

Fortunately, the outlook is not so bleak when it comes to block invoke functions. Block invoke functions not defined at global scope embed the name of the outermost enclosing function. The first block in a function f() will be named __f_block_invoke_1 if generated by gcc and __f_block_invoke_ if generated by clang. The numeric suffix is incremented prior to generating the name of the block invoke function of each subsequent block encountered within the function. (clang starts by appending the number 1 and increments it like gcc thereafter.) Objective-C method names will be embedded just as C function names, leading to block invoke function names like __-[Foo init]_block_invoke_1. C++ member functions are neither qualified nor mangled for embedding, so a block defined within the member function Foo::Bar() will cause the compiler to generate a block invoke function named __Bar_block_invoke_ (append a 1 if the compiler is gcc).

The block invoke functions of blocks defined at global scope are harder to track down, since they have no enclosing function to anchor them. gcc names such functions starting with __block_global_1. clang uses the same name scheme as for blocks defined within a function, only it substitutes global as the function name. Consequently, the first block defined at global scope that clang encounters is named __global_block_invoke_, and the second, __global_block_invoke_1.

One surprising result of the naming conventions for blocks at global scope is that when several source files are compiled into a single executable, each one can have its own __global_block_invoke_ function. The resulting executable will have several functions with the identical name distinguished by their being at different addresses.

Whenever possible, specify breakpoints in block functions using the file and line number rather than the name. The name generation scheme could change in future and does not guarantee uniqueness within a linked executable, only within a compilation unit.

  • + Share This
  • 🔖 Save To Your Account