Mach-O LTO handling of `linkonce_odr unnamed_addr`

Hi,

I’m working on LLD’s handling of linkonce_odr unnamed_addr symbols, which are basically treated as autohide symbols by ld64. However, I noticed that this was done in a somewhat awkward manner: the object files generated by the libLTO backend don’t always have the “right” visibility (i.e. they are not autohide), and so ld64 fixes things up after the fact by setting autohide on those object file symbols whose corresponding bitcode input file symbol has linkonce_odr unnamed_addr.

I’m wondering if I should follow suit for LLD-MachO’s implementation of LTO. It seems kind of hacky and probably noticeably slower than having the LTO backend emit the right visibility from the get go. However, I don’t understand enough about LTO yet to know if this behavior can be fixed.

It’s also interesting to note that regular LTO and thinLTO can emit different symbol visibilities for the same input… in particular, when linking the following:

target triple = "x86_64-apple-darwin"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"

@global_unnamed = linkonce_odr unnamed_addr global i8 42
@local_unnamed_const = linkonce_odr local_unnamed_addr constant i8 42
@local_unnamed = linkonce_odr local_unnamed_addr global i8 42
@used = global [3 x i8*] [i8* @global_unnamed, i8* @local_unnamed, i8* @local_unnamed_const]
@llvm.used = appending global [1 x [3 x i8*]*] [[3 x i8*]* @used]

The thinLTO backend sets autohide on _global_unnamed (and no other symbols), whereas regular LTO does not set autohide on any of them. I’m wondering if this indicates some missed optimization opportunity in regular LTO? Regardless, in both cases, ld64 fixes things up so that both _global_unnamed and _local_unnamed_const are autohide.

cc @cachemeifyoucan @teresajohnson

Jez

There are some context in this abandoned the review: ⚙ D43361 [ThinLTO] Enable AutoHide on symbols with local_unnamed_addr for the current ld64 behavior. I think the fixup in linker is mostly for thinLTO, rather than regular LTO.

I can’t remember the context for autohide decision from regular LTO. There is a general limitation for the libLTO API to convey the linkage information (either export or not export) and that contributes a lot to the current libLTO/ld64 implementation. Can you provide a reproducer (including the tool invocation) for the regular LTO dropping auto hide behavior so I can help take a look?

From your example, it does sound like @global_unnamed should be autohide in the machO object file produced by libLTO.

Here is the repro as a lit test (I put it under lld/test/MachO and invoked it as llvm-lit -vva <file>, but it should work in any LLVM test folder):

; REQUIRES: x86
; RUN: rm -rf %t; mkdir %t
; RUN: llvm-as %s -o %t/unnamed-addr.o
; COM: opt -module-summary %s -o %t/unnamed-addr.o

; RUN: ld -dylib -arch x86_64 -platform_version macos 10.15 11.0 -syslibroot \
; RUN:   $(xcrun -show-sdk-path) -lSystem %t/unnamed-addr.o -o %t/unnamed-addr \
; RUN:   -save-temps
; RUN: echo "libLTO output:"
; RUN: llvm-nm -m %t/unnamed-addr.*lto.o
; RUN: echo "final ld64 output:"
; RUN: llvm-nm -m %t/unnamed-addr

target triple = "x86_64-apple-darwin"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"

@global_unnamed = linkonce_odr unnamed_addr global i8 42
@local_unnamed_const = linkonce_odr local_unnamed_addr constant i8 42
@local_unnamed = linkonce_odr local_unnamed_addr global i8 42
@used = global [3 x i8*] [i8* @global_unnamed, i8* @local_unnamed, i8* @local_unnamed_const]
@llvm.used = appending global [1 x [3 x i8*]*] [[3 x i8*]* @used]

define void @main() {
  ret void
}

While putting it together, I realized that @global_unnamed is emitted as autohide by libLTO if we are linking an executable. It’s only with -dylib that it stops being hidden.

libLTO has no idea if output is executable or dylib. That is a ld64 behavior. When building for -dylib, ld64 will ask to preserve all the global symbols and all symbols that has global scope will be preserved.

I don’t see a check for autohide here: ld64/lto_file.cpp at master · apple-opensource/ld64 · GitHub

Also I am not sure just adding the check for autohide is semantically correct here. Need to give it more thinking.