[fpc-devel] Blocks support

Jonas Maebe jonas.maebe at elis.ugent.be
Wed Jul 16 14:12:42 CEST 2014


Hi,

I've (locally, not yet in svn) implemented support for blocks in FPC ( 
http://en.wikipedia.org/wiki/Blocks_(C_language_extension) ). Blocks are 
basically procedural variables with support for capturing local context 
in various ways. As implemented in C/C++/Objective-C, they also 
introduce the syntactic sugar of declaring them as inline code fragments 
(anonymous procdures), but I have not implemented this in FPC. Such 
functionality is completely orthogonal to the support for blocks, and 
can be added later if/when support for anonymous methods is added to FPC 
in general.

The current blocks support is very limited and only supports converting 
regular procedures/functions to blocks (i.e., not nested 
procedures/functions, not methods of either Pascal or Objective-Pascal 
classes/objects, not other procedural variables). It is also only 
enabled on Darwin ((Mac) OS X, iOS and iphonesim) for now, but since the 
blocks runtime is available on other platforms too, it can be enabled 
elsewhere over time.

The way I've implemented it right now is that a block-type is declared 
like a regular procedure variable, followed by "is block". E.g.:

type
   NSArrayEnumeratorBlock = procedure (obj: id; idx: NSUInteger; var 
stop: boolean) is block;

Unlike other procedure variable types, you cannot specify the calling 
convention of these "is block" types, as it is determined by the blocks 
runtime. Also unlike other procedure variable types, the compiler will 
automatically generate wrappers when assigning a procedure/function to 
such an "is block" type, so that the calling convention of the original 
procedure/function doesn't matter.

A complete example:

***
{$mode objfpc}
{$modeswitch objectivec2}
{$modeswitch blocks}

uses
   CocoaAll;

type
   NSArrayEnumeratorBlock = procedure (obj: id; idx: NSUInteger; var 
stop: boolean) is block;

   { normally part of the NSArray declaration, but not yet in
     our units since the released compilers don't support blocks
     yet }
   enumcategory = objccategory external (NSArray)
     procedure enumerateObjectsUsingBlock(block: 
NSArrayEnumeratorBlock); message 'enumerateObjectsUsingBlock:';
   end;


procedure myenumerate(obj: id; idx: NSUInteger; var stop: boolean);
begin
   NSLog(NSString.alloc.initWithUTF8String('Object at index %lu is %@'), 
idx, obj);
end;

var
   arr: NSMutableArray;
   str: NSString;
begin
   arr := NSMutableArray.alloc.init;
   str:=NSString.alloc.initWithUTF8String('abc');
   arr.addObject(str);
   str:=NSString.alloc.initWithUTF8String('123');
   arr.addObject(str);
   str:=NSString.alloc.initWithUTF8String('XYZ');
   arr.addObject(str);
   arr.enumerateObjectsUsingBlock(@myenumerate);
end.

Output:

2014-07-16 09:58:53.886 blockenumerate[17438:507] Object at index 0 is abc
2014-07-16 09:58:53.913 blockenumerate[17438:507] Object at index 1 is 123
2014-07-16 09:58:53.914 blockenumerate[17438:507] Object at index 2 is XYZ


Now, to the hard part. In case of global routines, the block does not 
capture any local state and everything works perfectly as it is. There 
is however not much point to using them this way, other than with 
library APIs that accept blocks rather than regular procedure variables 
(which forms an increasing part of the OS X/iOS system interface, so it 
is already useful by itself).

"Capturing local state" means that the block gets a copy or a reference 
to a variable that is local to the context where it is instantiated 
(instantiated in our case means "the place where we assign the address 
of a procedure/function to an "is block" variable). For example, when 
support is added for assigning an instance method to an "is block" 
variable, the instance pointer must become part of the block because 
otherwise we don't know what self parameter to use when the block is 
actually invoked.

However:

* Methods of Pascal classes

As explained above, the "self" parameter has to be captured by the block 
in this case. If nothing special is done, then this self pointer will be 
treated like a regular pointer and you will be responsible for freeing 
it. However, in case a block is passed to an asynchronous routine, the 
question will most likely be "when is it safe to free that instance", 
and I'm not sure how to do that. The blocks runtime supports adding code 
that will be executed when a block is copied and when it is freed, but 
since Pascal classes don't have an internal reference count, there's not 
much we can do automatically here.

There is a way to make a Pascal class reference counted, by making it a 
descendent of TInterfacedObject (or more generally, by implementing the 
IUnknown interface) and only handling it via interface variables, so 
maybe the compiler could generate special code in that case to make sure 
that the block does update the reference counts in that case. That way, 
you can have a choice between manual and automatic reference counting. I 
still have to think this through to determine whether this reference 
counted approach is in fact feasible to implement.

* Methods of Objective-C classes

Objective-C doesn't support function pointers to Objective-C methods (as 
far as I know), so right now Objective-Pascal doesn't support procedure 
variables that reference Objective-C methods either. If this is changed 
because of blocks, they should be supported in general for orthogonality 
reasons.

This would also require capturing the "self" parameter, but since 
Objective-C classes to come with reference counting (in various ways), 
the reference counting would be handled automatically behind the scenes. 
On the other hand, such a capture could result in cyclic references 
(https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html#//apple_ref/doc/uid/TP40011210-CH8-SW16 
)

* Local/nested procedures

These most closely resemble blocks as they exist in C, since they can 
directly access all local variables of their enclosing scope (rather 
than only a specific value such as the self pointer).

The default in C when accessing a local variable from the enclosing 
scope within a block depends on the type and declaration of the variable:
1) an Objective-C class or a variable marked to behave as such via 
__attribute__((NSObject)) (e.g. a CFString), or another block-typed 
variable: reference counts are automatically updated by the 
compiler/runtime -- except if that variable is declared as __weak, then 
no reference counting is performed
2) the variable is marked as "__block". In this case, the compiler and 
runtime use indirection (and if needed, copying onto the heap and 
updating all redirections) to make sure that the local variable can 
survive the scope in which it was declared, so that the block (and 
copies thereof) can keep using it even after the function in which the 
local variable was declared, returns
3) all other variables (non-block/object and not marked as __block) 
accessed by a block are simply copied into local storage of the block 
when the block is instantiated (in our case that would be: when the 
procedure pointer is assigned to to the "is block" variable). 
Modifications to such variables in the original routine after the block 
has been instantiated are not visible to the block and vice verse. If a 
copy is made of the block, then the current value of those variables 
within that first block are copied and afterwards again become private 
to the copy.

I currently have no idea how to encapsulate this in Pascal. We could add 
__block and __weak modifiers (the __weak modifier would also be useful 
for non-block-related Objective-Pascal once we add support for 
ARC/Automated Reference Counting), but that's not really nice.

Another option is to only support by-value copies, and basically always 
use cases 1) and 3) above depending on the type of the variable. If you 
want to change something by reference, then you would have to manually 
set up a pointer (or object instance) first.

Or maybe someone has another idea that combines the flexibility with a 
Pascalish syntax/way of working?


Jonas



More information about the fpc-devel mailing list