llvm-project icon indicating copy to clipboard operation
llvm-project copied to clipboard

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

Open NuriAmari opened this issue 1 year ago • 0 comments

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

NuriAmari avatar Sep 30 '24 20:09 NuriAmari