Objective-C and Distributed ThinLTO

I’m trying to use distributed ThinLTO to compile Objective-C files, here is a sample file reverse.mm :

#import <Foundation/Foundation.h>

NSString * reverse(NSString *str) {
    NSMutableString *reversedString = [NSMutableString stringWithCapacity:[str length]];
    [str enumerateSubstringsInRange:NSMakeRange(0,[str length]) 
                                options:(NSStringEnumerationReverse | NSStringEnumerationByComposedCharacterSequences)
                             usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
                            [reversedString appendString:substring];
                        }];
    return reversedString;
}

@interface SampleClass: NSObject
@end
 
@implementation SampleClass
 
- (void)start {
    NSString *res = reverse(@"abcd");
    NSLog(@"%@", res);
}

@end

int main() {
   SampleClass *tmp = [[SampleClass alloc] init];
   [tmp start];
}

I run the following commands:

clang++ -flto=thin -fobjc-arc -O2 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk reverse.mm -c
clang++ -fuse-ld=lld -flto=thin reverse.o -Wl,-syslibroot,/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -Wl,--thinlto-index-only
clang++ -c -x ir -O2 -fthinlto-index=reverse.o.thinlto.bc reverse.o -o native-reverse.o

And the last command ends with error:

fatal error: error in backend: Cannot select: intrinsic %llvm.objc.clang.arc.noop.use
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace, preprocessed source, and associated run script.
Stack dump:
0.      Program arguments: /Users/evgeniyzabolotniy/build/bin/clang++ -c -x ir -fobjc-arc -O2 -fthinlto-index=reverse.o.thinlto.bc reverse.o -o native-reverse.o
1.      Running pass 'Function Pass Manager' on module 'reverse.o'.
2.      Running pass 'X86 DAG->DAG Instruction Selection' on function '@"\01-[SampleClass start]"'
 #0 0x0000000106b0dc17 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x10247ec17)
 #1 0x0000000106b0bd58 llvm::sys::RunSignalHandlers() (/Users/evgeniyzabolotniy/build/bin/clang-17+0x10247cd58)
 #2 0x0000000106b0d250 llvm::sys::CleanupOnSignal(unsigned long) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x10247e250)
 #3 0x0000000106a63af5 (anonymous namespace)::CrashRecoveryContextImpl::HandleCrash(int, unsigned long) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x1023d4af5)
 #4 0x0000000106a63abe llvm::CrashRecoveryContext::HandleExit(int) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x1023d4abe)
 #5 0x0000000106b08d4c llvm::sys::Process::Exit(int, bool) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x102479d4c)
 #6 0x0000000104699169 LLVMErrorHandler(void*, char const*, bool) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x10000a169)
 #7 0x0000000106a6c6bd llvm::report_fatal_error(llvm::Twine const&, bool) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x1023dd6bd)
 #8 0x0000000107b7d67a llvm::SelectionDAGISel::CannotYetSelect(llvm::SDNode*) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x1034ee67a)
 #9 0x0000000107b7c47d llvm::SelectionDAGISel::SelectCodeCommon(llvm::SDNode*, unsigned char const*, unsigned int) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x1034ed47d)
#10 0x00000001057ba6f5 (anonymous namespace)::X86DAGToDAGISel::Select(llvm::SDNode*) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x10112b6f5)
#11 0x0000000107b73bbf llvm::SelectionDAGISel::DoInstructionSelection() (/Users/evgeniyzabolotniy/build/bin/clang-17+0x1034e4bbf)
#12 0x0000000107b731e8 llvm::SelectionDAGISel::CodeGenAndEmitDAG() (/Users/evgeniyzabolotniy/build/bin/clang-17+0x1034e41e8)
#13 0x0000000107b724ff llvm::SelectionDAGISel::SelectAllBasicBlocks(llvm::Function const&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x1034e34ff)
#14 0x0000000107b7007f llvm::SelectionDAGISel::runOnMachineFunction(llvm::MachineFunction&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x1034e107f)
#15 0x00000001057b1360 (anonymous namespace)::X86DAGToDAGISel::runOnMachineFunction(llvm::MachineFunction&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x101122360)
#16 0x0000000105ea5a4f llvm::MachineFunctionPass::runOnFunction(llvm::Function&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x101816a4f)
#17 0x00000001062b743a llvm::FPPassManager::runOnFunction(llvm::Function&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x101c2843a)
#18 0x00000001062bec73 llvm::FPPassManager::runOnModule(llvm::Module&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x101c2fc73)
#19 0x00000001062b8049 llvm::legacy::PassManagerImpl::run(llvm::Module&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x101c29049)
#20 0x0000000107c0ddda codegen(llvm::lto::Config const&, llvm::TargetMachine*, std::__1::function<llvm::Expected<std::__1::unique_ptr<llvm::CachedFileStream, std::__1::default_delete<llvm::CachedFileStream>>> (unsigned int, llvm::Twine const&)>, unsigned int, llvm::Module&, llvm::ModuleSummaryIndex const&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x10357edda)
#21 0x0000000107c0ef3a llvm::lto::thinBackend(llvm::lto::Config const&, unsigned int, std::__1::function<llvm::Expected<std::__1::unique_ptr<llvm::CachedFileStream, std::__1::default_delete<llvm::CachedFileStream>>> (unsigned int, llvm::Twine const&)>, llvm::Module&, llvm::ModuleSummaryIndex const&, llvm::StringMap<std::__1::unordered_set<unsigned long long, std::__1::hash<unsigned long long>, std::__1::equal_to<unsigned long long>, std::__1::allocator<unsigned long long>>, llvm::MallocAllocator> const&, llvm::DenseMap<unsigned long long, llvm::GlobalValueSummary*, llvm::DenseMapInfo<unsigned long long, void>, llvm::detail::DenseMapPair<unsigned long long, llvm::GlobalValueSummary*>> const&, llvm::MapVector<llvm::StringRef, llvm::BitcodeModule, llvm::DenseMap<llvm::StringRef, unsigned int, llvm::DenseMapInfo<llvm::StringRef, void>, llvm::detail::DenseMapPair<llvm::StringRef, unsigned int>>, std::__1::vector<std::__1::pair<llvm::StringRef, llvm::BitcodeModule>, std::__1::allocator<std::__1::pair<llvm::StringRef, llvm::BitcodeModule>>>>*, std::__1::vector<unsigned char, std::__1::allocator<unsigned char>> const&)::$_3::operator()(llvm::Module&, llvm::TargetMachine*, std::__1::unique_ptr<llvm::ToolOutputFile, std::__1::default_delete<llvm::ToolOutputFile>>) const (/Users/evgeniyzabolotniy/build/bin/clang-17+0x10357ff3a)
#22 0x0000000107c0ecbe llvm::lto::thinBackend(llvm::lto::Config const&, unsigned int, std::__1::function<llvm::Expected<std::__1::unique_ptr<llvm::CachedFileStream, std::__1::default_delete<llvm::CachedFileStream>>> (unsigned int, llvm::Twine const&)>, llvm::Module&, llvm::ModuleSummaryIndex const&, llvm::StringMap<std::__1::unordered_set<unsigned long long, std::__1::hash<unsigned long long>, std::__1::equal_to<unsigned long long>, std::__1::allocator<unsigned long long>>, llvm::MallocAllocator> const&, llvm::DenseMap<unsigned long long, llvm::GlobalValueSummary*, llvm::DenseMapInfo<unsigned long long, void>, llvm::detail::DenseMapPair<unsigned long long, llvm::GlobalValueSummary*>> const&, llvm::MapVector<llvm::StringRef, llvm::BitcodeModule, llvm::DenseMap<llvm::StringRef, unsigned int, llvm::DenseMapInfo<llvm::StringRef, void>, llvm::detail::DenseMapPair<llvm::StringRef, unsigned int>>, std::__1::vector<std::__1::pair<llvm::StringRef, llvm::BitcodeModule>, std::__1::allocator<std::__1::pair<llvm::StringRef, llvm::BitcodeModule>>>>*, std::__1::vector<unsigned char, std::__1::allocator<unsigned char>> const&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x10357fcbe)
#23 0x0000000106e87336 clang::EmitBackendOutput(clang::DiagnosticsEngine&, clang::HeaderSearchOptions const&, clang::CodeGenOptions const&, clang::TargetOptions const&, clang::LangOptions const&, llvm::StringRef, llvm::Module*, clang::BackendAction, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>, std::__1::unique_ptr<llvm::raw_pwrite_stream, std::__1::default_delete<llvm::raw_pwrite_stream>>) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x1027f8336)
#24 0x00000001072168bf clang::CodeGenAction::ExecuteAction() (/Users/evgeniyzabolotniy/build/bin/clang-17+0x102b878bf)
#25 0x00000001075feffa clang::FrontendAction::Execute() (/Users/evgeniyzabolotniy/build/bin/clang-17+0x102f6fffa)
#26 0x0000000107568ac6 clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x102ed9ac6)
#27 0x0000000107685508 clang::ExecuteCompilerInvocation(clang::CompilerInstance*) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x102ff6508)
#28 0x0000000104698b76 cc1_main(llvm::ArrayRef<char const*>, char const*, void*) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x100009b76)
#29 0x000000010469542c ExecuteCC1Tool(llvm::SmallVectorImpl<char const*>&, llvm::ToolContext const&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x10000642c)
#30 0x000000010737abbe void llvm::function_ref<void ()>::callback_fn<clang::driver::CC1Command::Execute(llvm::ArrayRef<std::__1::optional<llvm::StringRef>>, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>*, bool*) const::$_1>(long) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x102cebbbe)
#31 0x0000000106a63a98 llvm::CrashRecoveryContext::RunSafely(llvm::function_ref<void ()>) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x1023d4a98)
#32 0x000000010737a626 clang::driver::CC1Command::Execute(llvm::ArrayRef<std::__1::optional<llvm::StringRef>>, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>*, bool*) const (/Users/evgeniyzabolotniy/build/bin/clang-17+0x102ceb626)
#33 0x000000010733b52d clang::driver::Compilation::ExecuteCommand(clang::driver::Command const&, clang::driver::Command const*&, bool) const (/Users/evgeniyzabolotniy/build/bin/clang-17+0x102cac52d)
#34 0x000000010733b7a0 clang::driver::Compilation::ExecuteJobs(clang::driver::JobList const&, llvm::SmallVectorImpl<std::__1::pair<int, clang::driver::Command const*>>&, bool) const (/Users/evgeniyzabolotniy/build/bin/clang-17+0x102cac7a0)
#35 0x000000010735badf clang::driver::Driver::ExecuteCompilation(clang::driver::Compilation&, llvm::SmallVectorImpl<std::__1::pair<int, clang::driver::Command const*>>&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x102cccadf)
#36 0x00000001046945ac clang_main(int, char**, llvm::ToolContext const&) (/Users/evgeniyzabolotniy/build/bin/clang-17+0x1000055ac)
#37 0x00000001046a3bd4 main (/Users/evgeniyzabolotniy/build/bin/clang-17+0x100014bd4)
#38 0x00007ff803f7041f 
clang++: error: clang frontend command failed with exit code 70 (use -v to see invocation)
clang version 17.0.0 (https://github.com/llvm/llvm-project 820be30ad96591de2d7e651b3ec9cc0253ca6344)
Target: x86_64-apple-darwin22.5.0
Thread model: posix
InstalledDir: /Users/evgeniyzabolotniy/build/bin
clang++: note: diagnostic msg: Error generating preprocessed source(s) - no preprocessable inputs.

BTW, if I run local ThinLTO:

clang++ -flto=thin -fobjc-arc -O2 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk reverse.mm -c
clang++ -fuse-ld=/Users/evgeniyzabolotniy/build/bin/ld64.lld reverse.o -fobjc-arc -O2 -Wl,-syslibroot,/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk

It gets compiled and linked successfully.

How to make the distributed ThinLTO case work?

Don’t know if this is your issue, but in the first case you pass -fobjc-arc solely to the initial clang++ command, whereas for the second case you pass it to both.

Thanks, but unfortunately it’s not the case. Even if to add -fobjc-arc to every command, the error stays the same.

I don’t know much about ObjC compilation, but from a web search on that error, I found this issue: 49717 – LLVM ERROR: Cannot select: intrinsic %llvm.objc.clang.arc.use

That bug is for a manual invocation of opt, but essentially indicates that the -objc-arc-contract pass ( ObjCARCContractPass) needs to be run to handle that intrinsic. That pass is normally added by clang here: https://github.com/llvm/llvm-project/blob/main/clang/lib/CodeGen/BackendUtil.cpp#L589-L593

Working up the call stack, this handling would have been provoked by a call to EmitAssemblyHelper::EmitAssembly. However, in distributed ThinLTO backend handling we invoke the thinBackend and early return just before that here: https://github.com/llvm/llvm-project/blob/main/clang/lib/CodeGen/BackendUtil.cpp#L1301-L1306.

The thinBackend has its own opt and codegen pipeline setups. Likely a similar call to invoke ObjCARCContractPass needs to be added to the ThinLTO backend pipeline setup at a similar location, i.e. right before addPassesToEmitFile here: https://github.com/llvm/llvm-project/blob/main/llvm/lib/LTO/LTOBackend.cpp#L413

2 Likes

I’ve tried it, and it worked. Thanks a lot! Looks like I have to open a PR?

Great! LLVM doesn’t currently use pull requests, but instead uses Phabricator for code reviews (Code Reviews with Phabricator — LLVM 18.0.0git documentation). If you don’t plan to contribute to LLVM in the future, I can probably take care of the code review process and get this submitted. But it needs a small IR test case.

Let me know if you would like to do the review and commit the fix yourself or have me do it. If the latter can you attach here the IR of the test case (I don’t have the MacOSX sdk so likely can’t compile the ObjC code through clang myself). I.e. if you do “llvm-dis reverse.o” and attach the reverse.o.ll file that might be enough.

It looks like a good exercise, I’ll try to do it myself.

I’ve pushed the diff, but It needs some help – ⚙ D158258 [ThinLTO] Add -objc-arc-contract pass