[Pseudo-ObjC] Reconstruct @autoreleasepool and @synchronized(...) blocks - #8525
AngeloD2022 wants to merge 2 commits into
Conversation
4c9e11b to
47e0fd0
Compare
bdash
left a comment
There was a problem hiding this comment.
Thanks for the PR! The @autoreleasepool rendering is a nice addition. Factoring the handling of HLIL_BLOCK so it can be overridden by Obj-C rendering seems like generally the right shape.
The autorelease region matching needs some work, though.
The fundamental issue is that TryEmitBlockRegion only ever looks at the immediate children of a single block. Nothing requires that a pool's pops are all siblings of the push. An autorelease pool containing a conditional return, or a conditional break or continue when the pool is inside a loop, will result in multiple pops for a single push, with at least one at a deeper level. Those nested pops are missed by the current approach, so they'll survive into the output despite referencing a variable that is no longer visible:
while (true)
{
@autoreleasepool
{
...
if (cond)
{
// What is `context` that this refers to?
_objc_autoreleasePoolPop(context);
break;
}
}
...
}I think the shape you want is to pair pops with pushes using the pool handle rather than by position, and then elide any pop of a handle whose region is currently open, wherever it appears inside that region.
|
This snippet, compiled with #import <Foundation/Foundation.h>
int main(int argc, char** argv)
{
for (id obj in NSProcessInfo.processInfo.environment)
{
@autoreleasepool {
if (!obj)
break;
NSLog(@"%@", obj);
}
}
} |
|
Appreciate the review, Mark! I'll investigate these tomorrow as soon as I can. |
47e0fd0 to
e2698d1
Compare
|
I made |
9a8a910 to
4f93515
Compare
@autoreleasepool blocks@autoreleasepool and @synchronized(...) blocks
|
Thanks for taking the time to update the PR, and sorry it has taken me so long to get back to reviewing it. It looks like there's still a number of cases where the processing results in a rendering that is inconsistent with the code. Some are contrived and probably rare in real binaries. Others are more likely to show up. Wrong output1. Declarations inside the block are used after itThe matched range becomes a real // clang -arch arm64 -fobjc-arc -O0 -framework Foundation sync_early.m -o sync_early_O0
#import <Foundation/Foundation.h>
__attribute__((noinline)) void work(id x) { NSLog(@"%@", x); }
__attribute__((noinline)) int check(id x) { return [x length] > 3; }
__attribute__((noinline)) int sync_early(id a)
{
@synchronized(a) {
work(a);
if (check(a)) {
work(@"exit");
return 1;
}
work(@"in lock");
}
work(@"after lock");
return 0;
}
int main(void) { return sync_early(@"hello"); }// Pseudo Objective-C
{
id var_20 = nullptr;
_objc_storeStrong(&var_20, arg1);
id obj = [var_20 retain];
@synchronized(obj)
{
_work(var_20);
int32_t var_30;
int32_t var_14;
if (!_check(var_20))
{
_work(@"in lock");
var_30 = 0;
}
else
{
_work(@"exit");
var_14 = 1;
var_30 = 1;
}
}
[obj release];
if (!var_30) // out of scope
{
_work(@"after lock");
var_14 = 0; // out of scope
int32_t var_30_1 = 1;
}
_objc_storeStrong(&var_20, nullptr);
return (uint64_t)var_14; // out of scope
}The same happens with // clang -arch arm64 -fobjc-arc -O1 -framework Foundation two_exits.m -o two_exits_O1
#import <Foundation/Foundation.h>
__attribute__((noinline)) void work(id x) { NSLog(@"%@", x); }
__attribute__((noinline)) int check(id x) { return [x length] > 3; }
__attribute__((noinline)) int two_exits(id a, id b)
{
@autoreleasepool {
if (check(a)) {
work(@"exit a");
return 1;
}
if (check(b)) {
work(@"exit b");
return 2;
}
work(@"body");
}
work(@"after pool");
return 0;
}
int main(void) { return two_exits(@"hello", @"c"); }// Pseudo Objective-C
{
[arg1 retain];
[arg2 retain];
@autoreleasepool
{
struct __NSConstantString* const x0_3;
int64_t result;
int32_t x23;
if (!_check(arg1))
{
int32_t x0_5 = _check(arg2);
x23 = !x0_5 ? 1 : 0;
x0_3 = !x0_5 ? @"body" : @"exit b";
result = 2;
}
else
{
x23 = 0;
result = 1;
x0_3 = @"exit a";
}
_work(x0_3);
}
if (x23) // out of scope
{
_work(@"after pool");
result = 0; // out of scope
}
[arg2 release];
[arg1 release];
return result; // out of scope
}Also reproduces with 2. Nested
|
|
Lots of good catches here. Thanks for the review. I'll fix these ASAP! |
Translate autorelease pool runtime function calls to
@autoreleasepool {...}blocks.Original Objective-C snippet:
Pseudo-C snippet:
Equivalent Pseudo-Objective-C: