CodeGen help for a new feature in ObjC

Hello everyone,

I’m currently implementing a new feature for ObjC and ARC in Clang, and would like some advice regarding an idea to implement the code generation of that feature.

The feature is fairly simple in appearance, here is the syntax: __weak_capture(variableName);
You can only write that inside a Block ^{ } and the feature is only available under ARC.
I am done with the whole Sema part, all the errors and warnings are managed:

  • Error: used outside of a block
  • Error: referencing a non-local variable
  • Error: referencing a non-ObjC object variable
  • Error: referencing a __block variable
  • Error: referencing a class that does not support __weak references
  • Warning: __weak_capture() duplicate statements in the same block (the second one is ignored)
  • Warning: __weak_capture() a variable that is not actually used in the block (the whole statement is removed)

When writing __weak_capture(var); inside a block, the variable “var” is captured in the block structure as if it was __weak.
However, when the block is executed, the captured object is loaded and retained (as if it was moved inside a __strong local variable) for as long as the object is used inside the block. When the variable is not used anymore, it is released like any other __strong variable in a local scope.

So, my actual problem is in CodeGen. I’ve god the weak capture itself working, the compiler generates the objc_loadWeak() to store the captured variable in the block structure as a weak. What I’m missing is the retain and release inside the code and there I’m not sure how to work it out.

I was thinking about adding a map in the CodeGenFunction that would indicate which variables are captured weakly and when it’s time to retain it (on first use) check the map, emit an objc_retain(), and after the last use emit the objc_release. What do you think about that idea?

I have no idea how to detect the last use though, I know ARC already does that, but where?

Here is an example of how it would look with and without the use of that feature:

With the feature:

@interface MyClass : NSObject @end
@implementation MyClass { id obj; }

  • (void)method
    {
    void (^someBlock)(void) =
    ^{
    __weak_capture(self);

if(self == nil) return;

// obj referes to the weakly captured “self”
NSlog(@“%@”, obj);
};
}
@end

Without the feature:

@interface MyClass : NSObject @end
@implementation MyClass { id obj; }

  • (void)method
    {
    __weak MyClass *weakSelf = self;

void (^someBlock)(void) =
^{
MyClass *self = weakSelf;
if(self == nil) return;

// obj referes to the weakly captured “self”
NSlog(@“%@”, obj);
};
}
@end

This is because you're trying to generalize from straight-line code, where the scope and CFG concerns are trivial. In general, it is not possible to statically decide which use is the first use, and it is not possible to even dynamically decide which use is the last use. For example, when do you want the retains and releases to happen in this code?

  ^{ __weak_capture(self); for (…) { if (...) [self blah]; }

John.

I'll think I'll just KISS it and to the retain before the body emission and the do the release after the body implementation.

That seems workable. However, you should push a cleanup to do the release instead of just manually calling it; pushDestroy should do what you want.

John.

I have one last detail to solve before I'm done with this feature.

The principle of this feature is that the storage in the block structure (be it on the stack or the heap) is __weak, but the local variables and the variables inside the block are not __weak, they would be strong usually.

My problem is that I can't figure out how to make weak setup to happen, I know I have to call EmitARCInitWeak() to initialize the capture field, but I don't know what parameters I have to pass at this point. The place where my code is supposed to be inserted uses a fake POD loading to put the expected value into the variable... Trying to reduce the code doesn't work very well sadly.

This is going to be a bit challenging because the type of the captured object doesn't match the type of the original object, and the AST is built up around that assumption.

You might be able to make it work just in CGBlocks.cpp, without any AST changes.

Incidentally, you're aware that this work will not be welcome in trunk, right?

John.