[Dlang-study] [lifetime] Few root decisions to take on RC classes

Michel Fortin michel.fortin at michelf.ca
Sat Oct 31 20:35:25 PDT 2015


Le 31 oct. 2015 à 20:04, deadal nix <deadalnix at gmail.com> a écrit :
 
>> The mechanics of Objective-C ARC are the same as anything else. The only difference is the addition of autorelease: you need to insert an extra opAutorelease call when returning an autoreleased object, and an extra opInc call when receiving one in the caller. For autoreleased object references passed to a function by a pointer or by 'ref', add an opAutorelease before the call, and an opInc after. That's it. Then let the optimizer do elision as usual: opAutorelease counts as an opDec with the only difference being that it cannot be elided.
> 
> Not even close. ARC is absurdly complex (we even had someone that worked on it show up in the forum and basically tell us it was a bad idea to copy ARC). See for yourself :
> http://clang.llvm.org/docs/Block-ABI-Apple.html
> http://clang.llvm.org/docs/AutomaticReferenceCounting.html
> 
> There are a lot of historical reasons why ARC is that way. One things is sure we don't want to copy this as a model.

Perhaps the first sentence from me you quoted above was slightly miswritten. I should have written "The mechanics needed to obtain Objective-C-compatible automatic reference counting in D are the same as those for D objects." Anyway, before I elaborate on that, let me clear up a few points:

* Up to now, we discussed how the compiler can automatically insert opInc/opDec calls to support a form of automatic reference counting for D objects. There was no discussion about weak pointers for D objects, or about capturing local variables for inner functions or lambdas (or blocks in Objective-C). You're pointing to documentation addressing a much larger featureset, so it surely is more complex.

* I am not suggesting implementing Apple's version of ARC to get Objective-C compatibility. It's sometime useful to know how Apple did it and what terminology they use, which makes these documents useful. But as many other people, I have manually managed reference counts in Objective-C code for a long time before ARC came along (it's really not that hard, but it takes some time to learn), and I can tell you how things should work when integrating with D without looking at any document. My idea here is to propose a simple system based on the opInc/opDec proposal that was described here prior Objective-C became a topic, with the minimal additions required to get compatibility with Objective-C.

So it seems I'll have to go in the details.

As I said in the part you quoted, there are two additions needed related to autorelease. That's assuming D already supports non-consuming and consuming arguments. Here are the two changes:


1. Support for autoreleased return values. When returning a value, you just add an extra call to autorelease (denoted by {-1*}):

	@autoreleased NSObject getObject() {
		return object{+1}{-1*};
	}

and when receiving an autoreleased object from a function, the caller needs to insert an extra opInc (retain):

	NSObject o{+1} = getObject(){+1}{-1};
	o{-1}; // end of scope

and from there the usual elision rules apply. Just make sure the call to autorelease does not get elided inside the "getObject" function.


2. Support for autoreleased values returned by pointer (or "ref"). Typically an Objective-C function returning an error will look like this (D syntax):

	bool readFromFile(NSString path, @autoreleased NSError* error);

Such a function would try to read something at the given path, and if it fails it returns false and assigns an error to the object reference passed through the pointer (only if the pointer is not null). Here's some typical usage:

	NSError error;
	if (!readFromFile(path, &error)) { ... handle the error ... }

Here is how I suggest D should handle it from the caller side (I'm going to assign an initial value to `error` to make the case more general):

	NSError error{+1} = initialValue;
	if (!readFromFile(path, &error{-1*}{+1})) { ... }
	error{-1}; // end of scope

In other words, the caller autoreleases the value just before the call and retain it again after the call. Then usual elision rules apply, just make sure the call to autorelease does not get elided.

Inside the called function, at the beginning you add a call opInc on the @autoreleased parameter, and call autorelease at the end to keep things balanced:

	bool readFromFile(NSString path, @autoreleased NSError* error) {
		if (error && *error) *error{+1};
		// function body
		if (error && *error) *error{-1*};
	}

Again, optimise by eliding as usual but don't get rid of the final autorelease.

Note that Apple's ARC implements @autoreleased for parameters using a type attribute. I know Walter won't like the idea of adding a type attribute, so I figured a way to make it work as a storage class (at a small cost in efficiency for the NSError passing pattern).


That's all. Now you have Objective-C compatible reference counting in D.

I could elaborate on other ways to optimise things further, but I think at this stage it'd be premature optimization.

In fact, just discussing about Objective-C at this point sounds a little premature to me. Whoever implements support for autorelease will be able to do it based on whatever D will do for @rc objects. You just identify the differences and compensate for them at the right spots around function call boundaries (as I did here).

I think it'd be more important to talk about auto-nulling weak references. That's a general concept that is necessary if you want reference counting to be useful and safe at the same time.


-- 
Michel Fortin
michel.fortin at michelf.ca
https://michelf.ca




More information about the Dlang-study mailing list