Autolinking behavior of @_implementationOnly with static linking

I want to confirm that I'm using @_implementationOnly correctly with static library in autolinking aspect.

Currently, Foundation imports CoreFoundation with @_implementationOnly attribute other than Linux and Darwin platforms.

Modules imported with @_implementationOnly are not added to swift1_autolink_entries to hide the dependencies, and they will be linked as private dependencies so that users don't need to link the private dependencies explicitly.

But the private linking only works on dynamic linking.

For example, Wasm toolchain uses static linking and CoreFoundation is imported with @_implementationOnly from Foundation. The produced libFoundation.a doesn't have CoreFoundation contents, so the Foundation users need to link CoreFoundation and CoreFoundation's dependencies explicitly.

In this case, should we merge CoreFoundation and CoreFoundation's dependencies with libFoundation.a like below?

$ llvm-ar -M <<EOS
create libFoundation.a
addlib libFoundation.a
addlib libCoreFoundation.a
addlib libuuid.a
addlib libicui18n.a
save
end
EOS
3 Likes

Hi @millenomi @spevans, would you have a moment to take a look at this or redirect us to someone else who could help?

I agree with you that for the static Foundation libraries libFoundation.a, libFoundationNetworking.a and libFoundationXML.a the dependencies should be linked in, even on Linux.

I have asked @compnerd (who is the expert on this) about this before and I think the issue maybe that CMake couldn't merge the libraries or there was no way to express it in CMakeLists.txt or something like that. Can you get it working in CMake?

Note, the libicui18n.a should probably be linked into libswiftCore.a on WASM as they are also used by the stdlib not just Foundation, along with the other ICU libraries.

I think there is still some more work to be done on making CoreFoundation private since its module is still shipped with the toolchain and import CoreFoundation still works, but this should actually go away in the future.

1 Like

Merging CoreFoundation into Foundation is reasonable for static linking purposes.

Merging the ICU libraries into the single module IMO is not okay though. CoreFoundation and libuuid are part of the project, but merging in ICU like that is both problematic because it is a separate project and because you are not guaranteed that the data and the ICU libraries will match.

Also note that you should be able to work with non-LLVM librarians (e.g. MSVC's lib which does not support MRI scripts). So this would need to be done manually.

I agree with you. Then I think we need to add icui18n into autolink entries to avoid forcing users explicit linking. So is there a good way to add libicui18n.a into autolink entries from Swift? The only way I come up with is

  1. Create a modulemap for icui18n:
module icui18n {
  link "icui18n"
}
  1. Add import statement in Foundation
import icui18n

It's a little hacky, so I want to know if there is a simpler way.

Thanks for letting me know about the MSVC case. What do you think about changing CoreFoundation and libuuid to Object Libraries in CMake? I think it doesn't depend on archiver script and make it easy to express in CMake.

I think that for static builds, we could inject an additional member which has the autolinked dependencies manually generated.

  1. For ELF:
    we could use the .swift_autolink_1 section (via a trivial bit of assembly)
  2. For PE/COFF:
    we can inject a simple C file with #pragma comment(lib, "icui18n.lib")
  3. For MachO:
    we do nothing; static linking is not supported on MachO

For shared libraries, this is not a problem.

This avoids the additional module map and header paths that need to be injected, and instead moves the complexity to the build system where an additional file needs to be added via target_sources.

I had originally tried to use object libraries, but there were some issues that I ran into (I unfortunately do not remember the details). One case that I am worried about though is static builds - if you convert them to static libraries, they will still not be merged and makes handling more difficult. Furthermore, I'm not sure what the behaviour is for cross-project linking (e.g. XCTest links against Foundation, which will use the object library CoreFoundation). Is there a reason that you are interested in the object library? Does $<TARGET_OBJECTS:CoreFoundation> not give you what you need? The libraries are already built as static, so we shouldn't have any concerns about statically linking the content (PE/COFF differentiates between static and shared libraries and the objects cannot be used outside of the library type intended).

Sorry, my sentence was misleading. I meant that I want to add LINK_LIBRARY entry in Foundation.swiftmodule for icui18n.

Even though we inject autolink_entries section in object file of Foundation, the section won't be read by swift-autolink-extract, so it doesn't work well on ELF and other platforms using swift_autolink mechanism, I think.

As far as I investigated, there is no way to add LINK_LIBRARY entry to swiftmodule explicitly, so I'm planning to add an option to do that. What do you think about this approach?

e.g. swiftc -emit-module -public-autolink-library icui18n

Thanks! $<TARGET_OBJECTS:CoreFoundation> is exactly what I needed. It works very well!

I sent a PR to add a new option -public-autolink-library.

1 Like

5.4 toolchain was shipped with this issue, so import FoundationNetworking with -static-stdlib is now broken on Linux :cry:

I sent a patch to address this issue :syringe:

https://github.com/apple/swift-corelibs-foundation/pull/2996

1 Like