[RFC] Adding exported visibility-style to the IR to model XCOFF exported visibility

Background

The AIX linker and XCOFF object file format, while having some visibility styles semantically similar to ELF (eg.e hidden, protected) have some unique distinctions. For the AIX linker, global or weak symbols which have no visibility bits (i.e the field set to zero, similar to ELF STV_DEFAULT) are only exported if specified on an export list provided to the linker (under the default and customary options).

To address usages of visibility in applications which may expect a symbol to be default to being exported in a shared object (such as may be expected by the user with __attribute__((__visibility__(default))) on ELF targets), XCOFF has an additional visibility type called “exported” which indicates to the linker that the symbol should be explicitly globally exported.

The existing LLVM visibility styles (LLVM Language Reference Manual — LLVM 16.0.0git documentation) do not allow us to model this distinction between symbols with the “default” visibility style, which are rendered for XCOFF by the MC layer as symbols with no visibility type / bits, and ones which should instead have “exported” visibility type.

Proposal

To model this visibility type, the proposal is to add the “exported” visibility style to the existing LLVM IR visibility styles. This visibility would be largely similar to default, and ignored for non-XCOFF targets, but will allow the direct modelling of this visibility in the IR for XCOFF. This seems preferable then alternatives such as attributes, as this visibility style is indeed mutually exclusive with other known visibility styles such as hidden. Making this IR change will frontends such as clang to emit the correct visibility information for AIX targets based on the unique source level attributes and option settings in use.

Draft Implementation

A draft implementation is up on Phabricator as ⚙ D123951 [LLVM][AIX] Implement XCOFF exported visibility. Any feedback here or on the patch is most appreciated.

References

https://www.ibm.com/docs/en/aix/7.3?topic=l-ld-command
https://www.ibm.com/docs/en/aix/7.3?topic=formats-xcoff-object-file-format
https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=extension-types-visibility-attributes

XCOFF defaults to HIDDEN visibility and requires an explicit export file to behave like ELF DEFAULT visibility.

What is the additional functionality provided by EXPORT versus the existing levels? It seems that XCOFF wants DEFAULT to mean HIDDEN to match its semantics and wants an additional level to provide the semantics of DEFAULT visibility for XCOFF.

This also conflicts with all other toolchains that have implemented visibility attributes.

This sounds to me quite a lot like the dllexport visibility used on Windows (where there’s nothing similar to ELFs hidden visibility though). Can that be used for this purpose (or generalized), or is that too tied up with other Windows specifics?

(Reflecting some off thread discussion)

XCOFF defaults to HIDDEN visibility and requires an explicit export file to behave like ELF DEFAULT visibility.

XCOFF defaults to no/unspecified visibility, since visibility is optional for AIX assembly and XCOFF objects can have no setting for the visibility bits. While default is superficially similar to hidden, hidden is an explicit symbol visibility and seems it can be propagated through static links.

What is the additional functionality provided by EXPORT versus the existing levels? It seems that XCOFF wants DEFAULT to mean HIDDEN to match its semantics and wants an additional level to provide the semantics of DEFAULT visibility for XCOFF.

For XCOFF, we match the IR “default” to unspecified / no visibility which means not emitting any visibility attributes in the MC layer. Whether the symbol is then exported from the shared object then depends on the user supplied export list and flags provided to the linker.

As stated above, there are some semantic differences between a XCOFF symbol with hidden and unspecified visibility, so it seems desirable to continue to make them distinct in the IR.

This also conflicts with all other toolchains that have implemented visibility attributes.

This proposal explicitly leaves the front-end implementation intentionally unspecified. The frontend is will be free to map the source level visibility attributes in different ways under option control to maintain compatibility with the variance in the toolchain implementations (e.g. GCC and XL) on AIX, which is (presumably) what will be done in clang.

This is a good suggestion and we had considered this as well for a bit. I’d worry that overloading the meaning of the DLLStorageClass ultimately would end up being confusing to the existing IR users and the possibly disruptive to the existing targets (i.e. windows) using it. I’m no expert on the Windows target, but DLL import/export does indeed appears to have some additional codegen implications from a look through the codebase.

Conversely, modelling exported as a visibility-style in IR ends up being both quite straight forward and natural (since that’s what it is in the object file at the end of the day).

Unifying the visibility styles and DLLStorageClass seems like something worth exploring in the longer term though, there is certainly an overlap between the two, as in this case.

Yes, the dll storage has a bit more implications. Dllimport affects code generations notably, while dllexport normally doesn’t really change anything, other than adding an extra directive about it being exported. (However, inconsistent application of the dllexport attribute in C source can produce warnings though.) So with that in mind, I guess it might make sense to start out with this separately, and maybe later on transition the dllexport handling to use the new visibility setting.

Do note that I’m very much not authoritative on these matters, so maybe it’d be good to wait for someone else to speak up too.

I think it will be useful to provide very clear linker semantics of the XCOFF visibilities. Then people may suggest how/whether the existing facility Mach-O can express the XCOFF visibilities. The generic ABI has a STV_DEFAULT chapter (Symbol Table) which is fairly concise, yet covers everything a linker developer needs.

Note that it is ok to have similar enough but not exactly the same semantics.
In Mach-O, private_extern doesn’t use a new IR attribute, It just reuses hidden which maps to ELF STV_HIDDEN. ld64 performs symbol resolution a bit differently, but that’s ok as the code generation behavior for the IR hidden is similar enough between ELF/Mach-O.

@mstorsjo’s point about potential reuse of dllexported makes sense to me. If we can reuse that, we don’t need to take one value from the bitcode format. ( I am a little bit undecided whether we should let exported take the STV_INTERNAL value from the bitcode format. ModuleSummaryIndex has a visibility field which has just 2 bits, using a value for one purpose will discard others.)

FWIW Solaris has STV_EXPORTED, but its semantic is a bit different.

I think it will be useful to provide very clear linker semantics of the XCOFF visibility

I think that will be helpful, so I’ll attempt to summarize some of the information from the references above here (with some repeats from the thread above) as I understand it.

Summary of XCOFF visibility semantics

Firstly, a symbol for XCOFF doesn’t necessarily have visibility bits. If it doesn’t the AIX linker follows its usual semantics, which is to export no symbols in the dynamic loader section unless the user/toolchain does so by providing an export list containing those symbols to export.

XCOFF supports the following visibility modes, most roughly analogous to the ELF modes with some notable exceptions:

  1. Symbols are bound at link-edit time, and thus runtime pre-emption is impossible without turning on optional runtime linking.
  2. The AIX linker appears to observe the visibilities only for place symbols into the exports after resolution, without some of the restrictions of ELF. So a symbol weak and hidden may be over-ridden by an external strong definition.
Symbol Visibility Export Semantic
Internal Symbol is not exported. The address of the symbol must not be provided to other programs or shared objects, but the linker does not verify this.
Hidden Symbol is not exported
Protected Symbol is exported but cannot be rebound (or preempted), even if runtime linking is being used.
Exported Symbol is exported with the global export attribute.
[None] The linker takes no action to export the symbol without options and/or export lists

Current IR Mapping

hidden and protected can be mapped in the natural, direct fashion.

Currently we map default style in the IR to no visibility. As this is the IR assumed default, this provides the typical non-visibility enabled path that users expect.


I am a little bit undecided whether we should let exported take the STV_INTERNAL value from the bitcode format. ModuleSummaryIndex has a visibility field which has just 2 bits, using a value for one purpose will discard others.)

Ah, that’s unfortunate. I agree, it seem to have only accommodated the 4 typical ELF visibilities so if we add exported, we displace internal which may just cause us more problems in the future. Thanks for pointing that out

I guess I’ll try to adjust the implementation to follow the dllexported route for now then, and see how it works out :slightly_smiling_face: It does indeed seems to be the closest match in terms of the semantic of what currently exists in the IR.

FWIW Solaris has STV_EXPORTED , but its semantic is a bit different.

Yeah, we were speculating if the exported visibility style would be re-usable for that as well. I guess there is a larger question here about such new styles, but hopefully for the moment we can ward off this problem for XCOFF at least by re-using dllexported