Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xcode Clang does not treat non-escaping blocks as global blocks #9358

Open
NuriAmari opened this issue Sep 30, 2024 · 0 comments
Open

Xcode Clang does not treat non-escaping blocks as global blocks #9358

NuriAmari opened this issue Sep 30, 2024 · 0 comments

Comments

@NuriAmari
Copy link

I've noticed that the Clang included with Xcode16 (and possibly older), does not treat blocks that capture but are marked non-escaping as global blocks, while open source Apple Clang does. It seems like https://reviews.llvm.org/D49303 has effectively been reverted in Xcode's Clang.

I have a simple program:

#import <Foundation/Foundation.h>
@interface Foo: NSObject
- (void)takeABlock: (void (^NS_NOESCAPE)(void))block;
@end
@implementation Foo 
- (void)takeABlock: (void (^NS_NOESCAPE)(void))block {
    block();
}
@end
int main(int argc, char** argv) {
    Foo* foo = [Foo new];
    int captured_local = argc + 1;
    [foo takeABlock: ^{ printf("%d", captured_local); }];
}

If I dump the IR with open source Swift's Clang as follows:

$SWIFT_LLVM_PATH/clang++ -Oz -isysroot $(xcrun --show-sdk-path) -framework Foundation -S -emit-llvm simple-test.mm -o swift/simple-test.ll

I get the following definition for main:

; Function Attrs: minsize mustprogress norecurse optsize ssp uwtable(sync)
define noundef i32 @main(i32 noundef %argc, ptr nocapture noundef readnone %argv) local_unnamed_addr #1 {
entry:
  %block = alloca <{ ptr, i32, i32, ptr, ptr, i32 }>, align 8
  %0 = load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$_", align 8
  %1 = load ptr, ptr @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !11
  %call = tail call noundef ptr @objc_msgSend(ptr noundef %0, ptr noundef %1) #5
  %add = add nsw i32 %argc, 1
  store ptr @_NSConcreteGlobalBlock, ptr %block, align 8
  %block.flags = getelementptr inbounds <{ ptr, i32, i32, ptr, ptr, i32 }>, ptr %block, i64 0, i32 1
  store <2 x i32> <i32 -796917760, i32 0>, ptr %block.flags, align 8
  %block.invoke = getelementptr inbounds <{ ptr, i32, i32, ptr, ptr, i32 }>, ptr %block, i64 0, i32 3
  store ptr @__main_block_invoke, ptr %block.invoke, align 8
  %block.descriptor = getelementptr inbounds <{ ptr, i32, i32, ptr, ptr, i32 }>, ptr %block, i64 0, i32 4
  store ptr @"__block_descriptor_36_e5_v8\01?0l", ptr %block.descriptor, align 8
  %block.captured = getelementptr inbounds <{ ptr, i32, i32, ptr, ptr, i32 }>, ptr %block, i64 0, i32 5
  store i32 %add, ptr %block.captured, align 8, !tbaa !12
  %2 = load ptr, ptr @OBJC_SELECTOR_REFERENCES_.4, align 8, !invariant.load !11
  call void @objc_msgSend(ptr noundef %call, ptr noundef %2, ptr nocapture noundef nonnull %block) #5
  ret i32 0
}

We still seem to honor the change made in https://reviews.llvm.org/D49303. This is the version of Apple Clang I am testing with:

clang version 17.0.0 (https://github.com/swiftlang/llvm-project.git 95f3fb07f8f52940912fdb0ba6f58fd0fe0f2511)
Target: arm64-apple-darwin23.6.0
Thread model: posix
InstalledDir: /Users/nuriamari/git/swift-project/build/Ninja-RelWithDebInfoAssert/llvm-macosx-arm64/bin

But if I repeat with Xcode16's Clang like so:

xcrun clang++ -Oz -isysroot $(xcrun --show-sdk-path) -framework Foundation -S -emit-llvm simple-test.mm -o xcode/simple-test.ll

We don't treat the block as global even though it is marked with NS_NOESCAPE:

; Function Attrs: minsize norecurse optsize ssp uwtable(sync)
define i32 @main(i32 noundef %0, ptr nocapture noundef readnone %1) local_unnamed_addr #1 {
  %3 = alloca <{ ptr, i32, i32, ptr, ptr, i32 }>, align 8
  %4 = load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$_", align 8
  %5 = tail call ptr @objc_opt_new(ptr %4)
  %6 = add nsw i32 %0, 1
  store ptr @_NSConcreteStackBlock, ptr %3, align 8 ; the isa pointer is different
  %7 = getelementptr inbounds <{ ptr, i32, i32, ptr, ptr, i32 }>, ptr %3, i64 0, i32 1
  store <2 x i32> <i32 -1073741824, i32 0>, ptr %7, align 8 ; and the flags also reflect the difference
  %8 = getelementptr inbounds <{ ptr, i32, i32, ptr, ptr, i32 }>, ptr %3, i64 0, i32 3
  store ptr @__main_block_invoke, ptr %8, align 8
  %9 = getelementptr inbounds <{ ptr, i32, i32, ptr, ptr, i32 }>, ptr %3, i64 0, i32 4
  store ptr @"__block_descriptor_36_e5_v8\01?0l", ptr %9, align 8
  %10 = getelementptr inbounds <{ ptr, i32, i32, ptr, ptr, i32 }>, ptr %3, i64 0, i32 5
  store i32 %6, ptr %10, align 8, !tbaa !12
  call void @"objc_msgSend$takeABlock:"(ptr noundef %5, ptr noundef undef, ptr nocapture noundef nonnull %3) #4
  ret i32 0
}

Is there a reason this behavior was turned off in Xcode Clang? It seems beneficial to keep it. cc @ahatanaka @rjmccall

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant