www.digitalmars.com         C & C++   DMDScript  

digitalmars.dip.ideas - Objective-C Interop Extensions

reply Luna <luna foxgirls.gay> writes:


Currently DLang has, in some compilers, support for ObjectiveC 
types being declared. However, modern Objective-C code is 
strongly dependent on autorelease and auto release pools.
Without them Objective-C code leaks memory (see: 
https://developer.apple.com/documentation/quartzcore/cametallayer?language=objc).

To alleviate this, clang implements the concept of an 
` autoreleasepool` block, this block frees all Objective-C 
classes which have called `autorelease`. In the case of Metal, 
for example, a lot of internal data uses autorelease calls.

The ` autoreleasepool` blocks in clang are implemented via 2 
helper functions declared in the objective-c runtime 
`libobjc.dylib` or the like in alternate runtime implementations.
```d
extern(C) extern void* objc_autoreleasePoolPush();
extern(C) extern void  objc_autoreleasePoolPop(void*);
```

My proposal is to add 2 new hooks to the D runtime emitted by the 
compiler as well as a new keyword, `autoreleasepool`. This 
keyword should be followed by a block.

The runtime hooks should have a default implementation which do 
nothing.
```d
void* _d_pool_enter() { return null; }
void  _d_pool_leave(void*) { }
```

Whenever an `autoreleasepool` block is encountered by the 
compiler, the following code should be emitted.
```d
// Note: __ctx_ptr is just a placeholder, the name of the 
variable should be unique.
void* __ctx_ptr = _d_pool_enter();

// Code goes here

_d_pool_leave(__ctx_ptr);
```

autorelease pools should additionally, on macOS, automatically be 
generated in the runtime entry point and wrap the user D main 
function, and threads created in D should also include pool enter 
and leave calls.

If the D compiler links to a library defining 
`objc_autoreleasePoolPush` and `objc_autoreleasePoolPop`, the D 
runtime hooks should link against those, instead.



The `Foundation` library includes a class called 
NSAutoreleasePool, however i've found that if Objective-C's 
runtime is compiled with ARC (Automatic Reference Counting) 
support, then NSAutoreleasePool becomes no-op in some instances, 
which in turn causes memory leaks.
Nov 16
next sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Saturday, 16 November 2024 at 22:29:36 UTC, Luna wrote:


 Currently DLang has, in some compilers, support for ObjectiveC 
 types being declared. However, modern Objective-C code is 
 strongly dependent on autorelease and auto release pools.
 Without them Objective-C code leaks memory (see: 
 https://developer.apple.com/documentation/quartzcore/cametallayer?language=objc).

 To alleviate this, clang implements the concept of an 
 ` autoreleasepool` block, this block frees all Objective-C 
 classes which have called `autorelease`. In the case of Metal, 
 for example, a lot of internal data uses autorelease calls.

 The ` autoreleasepool` blocks in clang are implemented via 2 
 helper functions declared in the objective-c runtime 
 `libobjc.dylib` or the like in alternate runtime 
 implementations.
 ```d
 extern(C) extern void* objc_autoreleasePoolPush();
 extern(C) extern void  objc_autoreleasePoolPop(void*);
 ```

 My proposal is to add 2 new hooks to the D runtime emitted by 
 the compiler as well as a new keyword, `autoreleasepool`. This 
 keyword should be followed by a block.

 The runtime hooks should have a default implementation which do 
 nothing.
 ```d
 void* _d_pool_enter() { return null; }
 void  _d_pool_leave(void*) { }
 ```

 Whenever an `autoreleasepool` block is encountered by the 
 compiler, the following code should be emitted.
 ```d
 // Note: __ctx_ptr is just a placeholder, the name of the 
 variable should be unique.
 void* __ctx_ptr = _d_pool_enter();

 // Code goes here

 _d_pool_leave(__ctx_ptr);
 ```

 autorelease pools should additionally, on macOS, automatically 
 be generated in the runtime entry point and wrap the user D 
 main function, and threads created in D should also include 
 pool enter and leave calls.

 If the D compiler links to a library defining 
 `objc_autoreleasePoolPush` and `objc_autoreleasePoolPop`, the D 
 runtime hooks should link against those, instead.



 The `Foundation` library includes a class called 
 NSAutoreleasePool, however i've found that if Objective-C's 
 runtime is compiled with ARC (Automatic Reference Counting) 
 support, then NSAutoreleasePool becomes no-op in some 
 instances, which in turn causes memory leaks.
This would be easy to do, synchronised works in pretty much the exact same way, but why does this need to be a compiler thing? i.e. what does it offer that ```d struct AutoreleasePool { private void* __p; static AutoreleasePool opCall() { AutoreleasePool ret; ret.__p = objc_autoreleasePoolPush(); return ret; } ~this () { objc_autoreleasePoolPop(__p); } } void main() { auto x = AutoreleasePool(); } ``` does not? I think I saw some of your code that used a scope delegate as `autorelease((){ ... });` why is that not sufficient? Given how niche this is, there had better be a very good reason for adding it, e.g. generality with regular D code, not just Obj-C.
Nov 16
parent reply Luna <luna foxgirls.gay> writes:
On Saturday, 16 November 2024 at 22:47:09 UTC, Nicholas Wilson 
wrote:
 On Saturday, 16 November 2024 at 22:29:36 UTC, Luna wrote:


 Currently DLang has, in some compilers, support for ObjectiveC 
 types being declared. However, modern Objective-C code is 
 strongly dependent on autorelease and auto release pools.
 Without them Objective-C code leaks memory (see: 
 https://developer.apple.com/documentation/quartzcore/cametallayer?language=objc).

 To alleviate this, clang implements the concept of an 
 ` autoreleasepool` block, this block frees all Objective-C 
 classes which have called `autorelease`. In the case of Metal, 
 for example, a lot of internal data uses autorelease calls.

 The ` autoreleasepool` blocks in clang are implemented via 2 
 helper functions declared in the objective-c runtime 
 `libobjc.dylib` or the like in alternate runtime 
 implementations.
 ```d
 extern(C) extern void* objc_autoreleasePoolPush();
 extern(C) extern void  objc_autoreleasePoolPop(void*);
 ```

 My proposal is to add 2 new hooks to the D runtime emitted by 
 the compiler as well as a new keyword, `autoreleasepool`. This 
 keyword should be followed by a block.

 The runtime hooks should have a default implementation which 
 do nothing.
 ```d
 void* _d_pool_enter() { return null; }
 void  _d_pool_leave(void*) { }
 ```

 Whenever an `autoreleasepool` block is encountered by the 
 compiler, the following code should be emitted.
 ```d
 // Note: __ctx_ptr is just a placeholder, the name of the 
 variable should be unique.
 void* __ctx_ptr = _d_pool_enter();

 // Code goes here

 _d_pool_leave(__ctx_ptr);
 ```

 autorelease pools should additionally, on macOS, automatically 
 be generated in the runtime entry point and wrap the user D 
 main function, and threads created in D should also include 
 pool enter and leave calls.

 If the D compiler links to a library defining 
 `objc_autoreleasePoolPush` and `objc_autoreleasePoolPop`, the 
 D runtime hooks should link against those, instead.



 The `Foundation` library includes a class called 
 NSAutoreleasePool, however i've found that if Objective-C's 
 runtime is compiled with ARC (Automatic Reference Counting) 
 support, then NSAutoreleasePool becomes no-op in some 
 instances, which in turn causes memory leaks.
This would be easy to do, synchronised works in pretty much the exact same way, but why does this need to be a compiler thing? i.e. what does it offer that ```d struct AutoreleasePool { private void* __p; static AutoreleasePool opCall() { AutoreleasePool ret; ret.__p = objc_autoreleasePoolPush(); return ret; } ~this () { objc_autoreleasePoolPop(__p); } } void main() { auto x = AutoreleasePool(); } ``` does not? I think I saw some of your code that used a scope delegate as `autorelease((){ ... });` why is that not sufficient? Given how niche this is, there had better be a very good reason for adding it, e.g. generality with regular D code, not just Obj-C.
There's a few subtleties that would make this change in particular good to have on macOS at least. Given that, as said, threads and the runtime entry and exits needs to have implicit autoreleasepools to avoid potentially leaking both memory, but more importantly: kernel handles. A lot of functionality in macOS relies on getting shared memory handles that are wrapped in autorelease handlers; and without a pool to clean them up you can end up with a bunch of zombie handles exhausting the available handles in the system. Having this implicit and explicit control over autorelease behaviour would be a massive benefit and memory safety improvement when working on cross-platform software in D, as you can not avoid relying on Objective-C, eg. if you're making graphical applications.
Nov 16
parent Luna <luna foxgirls.gay> writes:
On Sunday, 17 November 2024 at 00:09:06 UTC, Luna wrote:
 On Saturday, 16 November 2024 at 22:47:09 UTC, Nicholas Wilson 
 wrote:
 On Saturday, 16 November 2024 at 22:29:36 UTC, Luna wrote:
 [...]
This would be easy to do, synchronised works in pretty much the exact same way, but why does this need to be a compiler thing? i.e. what does it offer that ```d struct AutoreleasePool { private void* __p; static AutoreleasePool opCall() { AutoreleasePool ret; ret.__p = objc_autoreleasePoolPush(); return ret; } ~this () { objc_autoreleasePoolPop(__p); } } void main() { auto x = AutoreleasePool(); } ``` does not? I think I saw some of your code that used a scope delegate as `autorelease((){ ... });` why is that not sufficient? Given how niche this is, there had better be a very good reason for adding it, e.g. generality with regular D code, not just Obj-C.
There's a few subtleties that would make this change in particular good to have on macOS at least. Given that, as said, threads and the runtime entry and exits needs to have implicit autoreleasepools to avoid potentially leaking both memory, but more importantly: kernel handles. A lot of functionality in macOS relies on getting shared memory handles that are wrapped in autorelease handlers; and without a pool to clean them up you can end up with a bunch of zombie handles exhausting the available handles in the system. Having this implicit and explicit control over autorelease behaviour would be a massive benefit and memory safety improvement when working on cross-platform software in D, as you can not avoid relying on Objective-C, eg. if you're making graphical applications.
I will add that the code I shared on the D Language Code Club discord still leaks ~10 objects on exit (but no kernel handles) due to a pool not being created before the objective-c runtime is loaded. This however is serviceable, as the memory gets freed anyway when the process exits. However if you are opening file handles without an autorelease pool you may end up leaking handles if you forget creating one. Having that handled automatically would be of great benefit in ensuring correct behaviour. However there's a single exception to autorelease pools, and that's Objective-C exceptions. Those implicitly free pool objects and don't need special handling, from my understanding. As such pools should only be freed when its scope exits *without any Objective-C exception being thrown*. D exceptions don't trigger this behaviour though, so there might need to be special handling for uncaught exceptions and errors.
Nov 16
prev sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
Me and Luna have talked a bit on Discord about this, and I've done quite 
a bit of reading.

With ARC (adding of retain/release automatically) we do not need release 
pool support. However while we might not need it, there is a lot of code 
out there that is not compiled with ARC which we will want to use.

This does place an importance on us adding reference counting that would 
be objective-c/COM aware.

Regarding code-gen, it is equivalent to a ``scope(exit)`` to perform the 
pool leaving.

```d
void main()  autorelease {

}
```

Is equivalent to:

```d
void main() {
     void* __generatedIdentifier1 = objc_autoreleasePoolPush();
     bool __generatedIdentifier2 = true;

     try {
     } catch (NSException e) {
         __generatedIdentifier2 = false;
         throw e;
     } finally {
         if (__generatedIdentifier2)
             objc_autoreleasePoolPop(__generatedIdentifier);
     }
}
```

The reason for the guard variable is that NSException effectively 
inherits from ``Error`` not ``Exception``. It is not meant to be caught. 
The lifetime of the auto release memory must outlive the catch.

Due to this exception behavior, this would be a valuable addition to our 
Objective-c support.

For the grammar:

```diff
ScopeStatement:
+    autorelease BlockStatement

AtAttribute:
+    autorelease
```

```d
void func()  autorelease {
	 autorelease {

	}
}
```

Mangling is unaffected by the attribute on the function.

https://clang.llvm.org/docs/AutomaticReferenceCounting.html

As for:

```d
void* _d_pool_enter() { return null; }
void  _d_pool_leave(void*) { }
```

I would suggest not doing it, it doesn't add anything.

Either its linked in on non-Apple platforms and then turned on by 
compiler switch, or it is not emitted.
Nov 16