1

I've got a strange situation. I have some local variables in a function:

JSContext *cx = ...;
jsval successCb = ...;

There is a function call which takes these parameters:

//JS_RemoveValueRoot(JSContext *cx, jsval *vp);
JS_RemoveValueRoot(cx, &successCb); //works

The above compiles fine. However, if I instead have the following, I get a compile time error:

id foo = ^() {
    JS_RemoveValueRoot(cx, &successCb);
}

Literally, if I copy and paste the line, if it's outside of the block it compiles, yet if it's not, it doesn't. The error is:

No matching function for call to 'JS_RemoveValueRoot'

I suspect something is going on behind the scenes in terms of how block closures are implemented but I'm not familiar enough with Objective C to figure this out. Why does this generate a compile-time error and how do I fix it?

EDIT: It seems that if I do the following I no longer get a compile-time error, but this makes no sense to me, which is always a bad thing, so I'd still like an explanation...

id foo = ^() {
    jsval localSuccessCb = successCb;
    JS_RemoveValueRoot(cx, &localSuccessCb);
};
Claudiu
  • 224,032
  • 165
  • 485
  • 680

2 Answers2

1

Ah I believe this is the issue. From this article on closures:

Here comes a first difference. The variables available in a block by closure are typed as «const». It means their values can't be modified from inside the block.

Thus the error is that I was passing JS_RemoveValueRoot a const jsval * instead of a jsval *. Creating a local copy that wasn't constant "resolved" the issue (depending on whether that behavior is acceptable, which in this case it is).

Alternatively I could also declare the jsval as:

__block jsval successCb = ...;

In which case I don't have to create a local non-const copy.

XCode did provide quite the unhelpful error message in this case...

Claudiu
  • 224,032
  • 165
  • 485
  • 680
1

It's more complicated that that. Yes, the immediate issue is that all non-__block captured variables are const inside the block. Therefore, inside the block cx has type JSContext * const and successCb has type const jsval. And const jsval * cannot be passed to jsval *. But you have to first understand why the variables are const.

Blocks capture non-__block variables by value at the time that they are created. That means the copy of the variable inside the block and the copy outside are different, independent variables, even though they have the same name. If it were not const, you might be tempted to change the variable inside the block and expect it to change outside, but it does not. (Of course, the opposite problem still happens -- you can still change the variable outside the block, since it's not const, and wonder why it does not change inside the block.) __block resolves this issue by making it so there's only one copy of the variable, that is shared between the inside and outside of the block.

Then it's important to think about why a const variable is not sufficient. If you just need the value of the variable, then a const copy is just as well. When const won't work, usually it's because of the need to assign to the variable. We need to ask, what does JS_RemoveValueRoot that it requires a non-const pointer to the variable? Is it to assign to the variable? (And if it does, do we care about the new value outside the block? Because if not, we can just assign the const variable to a non-const variable inside the block.)

It turns out it's more complicated. According to the documentation of JS_Remove*Root, it neither uses the value of the variable pointed to, nor needs to set the variable; rather, it needs the address of the variable, and this needs to match the address passed to JS_Add*Root. (Actually, I am not even sure whether a const pointer is even needed for what they're doing.) I am assuming that JS_AddValueRoot was done in the body of the function that encloses the block, outside the block. (I assume this since you said successCb is a local variable, so it must be within this function; if it were within the block, it wouldn't make sense because then successCb could just be a local variable of the block, and thus not need to be captured.)

Because the address of the variable itself is significant, let us consider what happens in the various block variable capture modes. A non-__block variable is now clearly not appropriate, since there are two separate copies (and thus two separate addresses) for the inside and outside. Thus, the addresses given to Add and Remove won't match. A __block variable is shared, and is much better.

However, there is still an issue with __block variables that may make it not match -- the address of a __block variable may change over time! This goes into the specifics of how blocks are implemented. In the current implementation, a __block variable is held in a special structure (a kind of an "object") that starts out on the stack, but when any block capturing it is copied, it is "moved" to the heap as a dynamically-allocated structure. This is very similar to how block objects that capture variables start out on the stack, but is moved to the heap upon being copied. Putting it on the stack first is an optimization, and is not guaranteed to happen; but currently it does. The __block variable itself is actually an access of the variable inside this structure, accessed through a pointer that tracks where this structure is. As the structure is moved from the stack to the heap, you can see the value of the expression &successCb change. (This is not possible in normal C.) Therefore, to have matching addresses, you must ensure that the move has already occurred when you pass the address of the variable to Add. You may be able to do this by forcibly copying a block that captures it.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • oh if only you had posted this right away! I did indeed get bitten by the fact that the address of the `__block` var changed outside the block vs in. It ended up failing some assertions when garbage collecting because I added the root of the variable when it was still on the stack, which eventually became garbage. I ended up figuring out all the above but I had to piece it together from various online sources and also ask on the mozilla IRC to figure out the GC bug. Nice to have it all in one place. – Claudiu Oct 29 '13 at 15:06