Trying to understand a crash in BitcodeWriterPass

I’m seeing a crash in the BitcodeWriterPass in my experimental compiler build.

Here’s the backtrace in lldb…

(lldb) bt
* thread #4, stop reason = hit program assert
    frame #0: 0x00007ff81d3c6ffe libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x00007ff81d3fd1ff libsystem_pthread.dylib`pthread_kill + 263
    frame #2: 0x00007ff81d348d24 libsystem_c.dylib`abort + 123
    frame #3: 0x00007ff81d3480cb libsystem_c.dylib`__assert_rtn + 314
  * frame #4: 0x000000010dfeebc6 swift-frontend`llvm::StringRef::operator[](this=0x000000033dffb828, Index=0) const at StringRef.h:231:7
    frame #5: 0x000000010d722e0e swift-frontend`llvm::GlobalValue::getGlobalIdentifier(Name=(Data = "", Length = 0), Linkage=PrivateLinkage, FileName=(Data = "bin/AVR/CoreOperators.bc", Length = 24)) at Globals.cpp:165:7
    frame #6: 0x000000010d723065 swift-frontend`llvm::GlobalValue::getGlobalIdentifier(this=0x0000600002c6a5a0) const at Globals.cpp:183:10
    frame #7: 0x0000000108a113a9 swift-frontend`llvm::GlobalValue::getGUID(this=0x0000600002c6a5a0) const at GlobalValue.h:596:41
    frame #8: 0x000000010cab20d7 swift-frontend`llvm::ModuleSummaryIndex::getOrInsertValueInfo(this=0x000000033dffcdd0, GV=0x0000600002c6a5a0) at ModuleSummaryIndex.h:1352:39
    frame #9: 0x000000010cab1e2f swift-frontend`findRefEdges(Index=0x000000033dffcdd0, CurUser=0x00000001c8e60810, RefEdges=0x000000033dffc2e8, Visited=0x000000033dffc0f0) at ModuleSummaryAnalysis.cpp:114:33
    frame #10: 0x000000010caace45 swift-frontend`computeFunctionSummary(Index=0x000000033dffcdd0, M=0x00000001c8626510, F=0x0000600002c6a008, BFI=0x0000600000039248, PSI=0x000060000e68e588, DT=0x000000033dffca98, HasLocalsInUsedOrAsm=true, CantBePromoted=0x000000033dffcbe0, IsThinLTO=false, GetSSICallback=function<const llvm::StackSafetyInfo *(const llvm::Function &)> @ 0x000000033dffc8a0)>) at ModuleSummaryAnalysis.cpp:330:7
    frame #11: 0x000000010caabd72 swift-frontend`llvm::buildModuleSummaryIndex(M=0x00000001c8626510, GetBFICallback=function<llvm::BlockFrequencyInfo *(const llvm::Function &)> @ 0x000000033dffcd40, PSI=0x000060000e68e588, GetSSICallback=function<const llvm::StackSafetyInfo *(const llvm::Function &)> @ 0x000000033dffcd00)>, llvm::ProfileSummaryInfo*, std::__1::function<llvm::StackSafetyInfo const* (llvm::Function const&)>) at ModuleSummaryAnalysis.cpp:800:5
    frame #12: 0x000000010caaeaa4 swift-frontend`llvm::ModuleSummaryIndexAnalysis::run(this=0x0000600000012098, M=0x00000001c8626510, AM=0x000000033dffd6d0) at ModuleSummaryAnalysis.cpp:892:10
    frame #13: 0x00000001082fb788 swift-frontend`llvm::detail::AnalysisPassModel<llvm::Module, llvm::ModuleSummaryIndexAnalysis, llvm::PreservedAnalyses, llvm::AnalysisManager<llvm::Module>::Invalidator>::run(this=0x0000600000012090, IR=0x00000001c8626510, AM=0x000000033dffd6d0) at PassManagerInternal.h:324:14
    frame #14: 0x000000010d8bdcd3 swift-frontend`llvm::AnalysisManager<llvm::Module>::getResultImpl(this=0x000000033dffd6d0, ID=0x000000010fe19190, IR=0x00000001c8626510) at PassManagerImpl.h:73:35
    frame #15: 0x000000010833ed32 swift-frontend`llvm::ModuleSummaryIndexAnalysis::Result& llvm::AnalysisManager<llvm::Module>::getResult<llvm::ModuleSummaryIndexAnalysis>(this=0x000000033dffd6d0, IR=0x00000001c8626510) at PassManager.h:778:9
    frame #16: 0x0000000108a2a252 swift-frontend`llvm::BitcodeWriterPass::run(this=0x000060000028d328, M=0x00000001c8626510, AM=0x000000033dffd6d0) at BitcodeWriterPass.cpp:23:31
    frame #17: 0x0000000100d2b787 swift-frontend`llvm::detail::PassModel<llvm::Module, llvm::BitcodeWriterPass, llvm::PreservedAnalyses, llvm::AnalysisManager<llvm::Module> >::run(this=0x000060000028d320, IR=0x00000001c8626510, AM=0x000000033dffd6d0) at PassManagerInternal.h:88:17
    frame #18: 0x000000010d8ba3ea swift-frontend`llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module> >::run(this=0x000000033dffd610, IR=0x00000001c8626510, AM=0x000000033dffd6d0) at PassManager.h:517:40
    frame #19: 0x0000000100cca33c swift-frontend`swift::performLLVMOptimizations(Opts=0x000000018181ff60, Module=0x00000001c8626510, TargetMachine=0x0000000198b98000, out=0x000000033dffedb8) at IRGen.cpp:406:20
    frame #20: 0x0000000100ccb53b swift-frontend`swift::performLLVM(Opts=0x000000018181ff60, Diags=0x0000000181820608, DiagMutex=0x000000033c7ef430, HashGlobal=0x0000000000000000, Module=0x00000001c8626510, TargetMachine=0x0000000198b98000, OutputFilename=(Data = "bin/AVR/CoreFloatingPointFunctions.bc\xff\xff\xff\xc8kh\x90\U00000001", Length = 37), Stats=0x0000000000000000) at IRGen.cpp:590:3
    frame #21: 0x0000000100d096ff swift-frontend`(anonymous namespace)::LLVMCodeGenThreads::Thread::run(this=0x00006000062f81c0) at IRGen.cpp:1257:9
    frame #22: 0x0000000100d0943d swift-frontend`(anonymous namespace)::LLVMCodeGenThreads::runThread(arg=0x00006000062f81c0) at IRGen.cpp:1286:13
    frame #23: 0x00007ff81d3fd4e1 libsystem_pthread.dylib`_pthread_start + 125
    frame #24: 0x00007ff81d3f8f6b libsystem_pthread.dylib`thread_start + 15

The GlobalValue in question has a Name that’s a StringRef == “”, so the code in GlobalValue::getGlobalIdentifier cannot check subscript [0] to see if it’s == ‘\1’.

At first I thought "that doesn’t make sense, how can a GlobalValue have a name that’s “”, the empty string?

But it seems this is just my naivety about how LLVM works?

I tried updating a count of the number of times a GlobalVariable was created in my (fairly complex) program I’m compiling* that has an empty string name “” …

  GlobalValue(Type *Ty, ValueTy VTy, Use *Ops, unsigned NumOps,
              LinkageTypes Linkage, const Twine &Name, unsigned AddressSpace)
      : Constant(PointerType::get(Ty, AddressSpace), VTy, Ops, NumOps),
        ValueType(Ty), Visibility(DefaultVisibility),
        UnnamedAddrVal(unsigned(UnnamedAddr::None)),
        DllStorageClass(DefaultStorageClass), ThreadLocal(NotThreadLocal),
        HasLLVMReservedName(false), IsDSOLocal(false), HasPartition(false),
        HasSanitizerMetadata(false) {
    setLinkage(Linkage);
    setName(Name);
   // this is the debug code I temporarily added in GlobalValue.h...
    if (Name.isSingleStringRef() && Name.getSingleStringRef().empty()) {
      countOfGlobalValueNullNames++;
    }
  }

…and it was loads, like over 5,000!

I wouldn’t expect a GlobalValue to have an empty name?

Any help or advice anyone can give would be much appreciated!

Carl
*
(I’m compiling a special custom version of the swift standard library.)

I’d bet most of them get setName called at some point after creation. The other genuine use-case might be for internal variables: global in nature but only accessible from that module so the name often doesn’t matter.

Some quick tests suggest LLVM does support them even for real globals though:

@0 = global i32 0

passes happily through llvm-as, llvm-dis, opt -verify and llc (it gets called __unnamed_1). Even works if it’s external, though probably not usefully.

1 Like

I believe this is the assert you get when you’re using ThinLTO and failed to run the NameAnonGlobals pass.

1 Like

Ah cool… yes, that makese sense! I’m not sure how the Swift code I have ended up doing this. My guess is because I’m targeting an unusual back end, the pass wasn’t added to the pass manager. Let me take a look.

Thanks so much guys! I’ll let you know what I find out…

EDIT

This code inside Swift (IRGen.cpp) seems like it activates ThinLTO on non Apple platforms (like mine) if you’re writing out Bitcode (as I do).

...
  case IRGenOutputKind::LLVMBitcode: {
    // Emit a module summary by default for Regular LTO except ld64-based ones
    // (which use the legacy LTO API).
    bool EmitRegularLTOSummary =
        TargetMachine->getTargetTriple().getVendor() != llvm::Triple::Apple;

    if (Opts.LLVMLTOKind == IRGenLLVMLTOKind::Thin) {
      PassManagerToRun.addPass(ThinLTOBitcodeWriterPass(*out, nullptr));
    } else {
      if (EmitRegularLTOSummary) {
        Module->addModuleFlag(llvm::Module::Error, "ThinLTO", uint32_t(0));
        // Assume other sources are compiled with -fsplit-lto-unit (it's enabled
        // by default when -flto is specified on platforms that support regular
        // lto summary.)
        Module->addModuleFlag(llvm::Module::Error, "EnableSplitLTOUnit",
                              uint32_t(1));
      }
      PassManagerToRun.addPass(BitcodeWriterPass(
          *out, /*ShouldPreserveUseListOrder*/ false, EmitRegularLTOSummary));
    }
    break;
...

I’m thinking I can just add a NameAnonGlobals pass before the BitcodeWriterPass and it should resolve the issue.

p.s. I don’t plan to do any form of LTO at link time for the moment (most users making programs with my tools are making very small programs that wouldn’t benefit much from LTO) but it seems like it might be easier to just add NameAnonGlobals pass rather than trying to remove ThinLTO, if this is the direction of travel for most people anyway.

EDIT

(previous post merged into the one above as suggested by Discourse)

This fixed it! I’m not sure how or why Swift started to enable ThinLTO support in bitcode exported for non Apple platforms, but it seems like in my slightly niche(?) case, it can cause a crash due to what I’ll call “anonymous globals”.

The reason for the crash seems to be… BitcodeWriterPass takes the bool parameter EmitSummaryIndex, which is being set true in the above (Swift compiler) code I showed in the previous post (in the case when you’re writing out bitcode for non Apple platforms, as I am). This calls ModuleSummaryIndexAnalysis which, as part of its processing, is creating summaries of GlobalValues, this tries to create a GUID from the Name of the GlobalValue. This sort of obviously fails with ‘anonymous’ GlobalValues because you can’t create globally unique identifiers based on name when there might be multiple global values in a bitcode file all with the Name “”.

The fix I used (I’m not sure if it’s right as I don’t know the bigger picture in the Swift compiler) as suggested by @nikic was to add a NameAnonGlobals pass before the BitcodeWriterPass in these cases in the Swift compiler code…

  case IRGenOutputKind::LLVMBitcode: {
    // Emit a module summary by default for Regular LTO except ld64-based ones
    // (which use the legacy LTO API).
    bool EmitRegularLTOSummary =
        TargetMachine->getTargetTriple().getVendor() != llvm::Triple::Apple;

    if (Opts.LLVMLTOKind == IRGenLLVMLTOKind::Thin) {
      PassManagerToRun.addPass(ThinLTOBitcodeWriterPass(*out, nullptr));
    } else {
      if (EmitRegularLTOSummary) {
        Module->addModuleFlag(llvm::Module::Error, "ThinLTO", uint32_t(0));
        // Assume other sources are compiled with -fsplit-lto-unit (it's enabled
        // by default when -flto is specified on platforms that support regular
        // lto summary.)
        Module->addModuleFlag(llvm::Module::Error, "EnableSplitLTOUnit",
                              uint32_t(1));
        PassManagerToRun.addPass(NameAnonGlobalPass());
      }
      PassManagerToRun.addPass(BitcodeWriterPass(
          *out, /*ShouldPreserveUseListOrder*/ false, EmitRegularLTOSummary));
    }
    break;

This works for me. The crash is gone and the compiler seems to be back to behaving.

I’ll take this fix over to the Swift community as a suggested PR (assuming it hasn’t already been found and fixed by someone else) and see what they think.

Thanks so much @nikic and @TNorthover … I had been stuck on this for a couple of weeks. You just helped me get the next version of Swift for Arduino ready for release I think!

Carl