Implementing Parts of the Swift Compiler in Swift

At present it is monolithic, but I would like to split it up at some point. It mostly comes down to my lack of experience in packaging and already having my hands full to get everything in as-is for now. I'm copying @Ron_Olson and his work on Fedora which puts everything into /usr/libexec/swift/ and I hope to be able to use that work as an example for the Debian team to approve of me doing so as well.

Will do. At present it seems that many of the issues that I am seeing the lint tool dig up are already present in the llvm-toolchain package in Debian. Nothing really seems to be coming from the swift code itself. There might be some paths in libraries that could be a swift build script issue, but I am still looking into that as it might not be a problem. For example:

E: swiftlang: custom-library-search-path RUNPATH swift_5.6.2/build/buildbot_linux/llvm-linux-x86_64/lib [usr/libexec/swift/bin/lldb-argdumper]
N: 
N:   The binary or shared library sets RPATH or RUNPATH. This overrides the
N:   normal library search path, possibly interfering with local policy and
N:   causing problems for multilib, among other issues.
N:   
N:   The only time a binary or shared library in a Debian package should set
N:   RPATH or RUNPATH is if it is linked to private shared libraries in the
N:   same package. In that case, place those private shared libraries in
N:   /usr/lib/*package*. Libraries used by binaries in other packages should be
N:   placed in /lib or /usr/lib as appropriate, with a proper SONAME, in which
N:   case RPATH/RUNPATH is unnecessary.
N:   
N:   To fix this problem, look for link lines like: gcc test.o -o test
N:   -Wl,--rpath,/usr/local/lib or gcc test.o -o test -R/usr/local/lib and
N:   remove the -Wl,--rpath or -R argument. You can also use the chrpath
N:   utility to remove the RPATH.
N: 
N:   Please refer to https://wiki.debian.org/RpathIssue for details.

I set swiftlang as a build dependency then build using the swift 5.6.2 installed on the system and it ran fine with no errors or lintian messages that I could see. So yes, it seems that it will be fine to have Swift build itself on Debian.

Hey Doug, I've been hearing recently from you and other Swift devs that the plan is to get rid of the bootstrap mode detailed here a couple years ago and make a prebuilt Swift compiler a requirement to build the Swift compiler, with the new swift-syntax parser written in Swift becoming a mandatory part of the compiler. I am fine with that new plan, but that means the only way to build the Swift compiler for new platforms on which a prebuilt Swift compiler doesn't exist will be to cross-compile it initially, ie the first cross-compilation scenario you laid out above.

Unfortunately, cross-compilation CMake config for the new Swift portions of the Swift compiler doesn't exist. @Erik_Eckstein added support to SwiftCompilerSources/ a couple years ago, but that has not been extended to the swift-syntax parser. I have filed an issue for this and submitted some pulls for non-Darwin platforms, but that has been met with silence and some strange assertions in the pulls.

Can you lay out the plan for when you want to drop this bootstrap process, before the 6.0 branch? And will there be work done on supporting cross-compiling the Swift portions of the Swift compiler before that? I note that the OSS Swift toolchain cannot even be cross-compiled from macOS x86_64 to macOS arm64 or vice versa with the current build config. @Max_Desiatov pointed out I was wrong about macOS cross-compilation, but the point stands for non-Darwin.

2 Likes

I don't think that's true. IIRC the default toolchain preset builds it for x86_64 and arm64, and that's how downloadable macOS toolchains distributed an Swift.org - Download Swift page turn out to be universal binaries supporting both CPU architectures in the same distribution archive.

1 Like

You are right, a recent macOS toolchain build shows both arches being built. I have never built Swift for a Darwin platform, so I thought the lack of CROSSCOMPILE-WITH-HOSTLIBS config outside SwiftCompilerSources/ meant the swift-syntax parser wouldn't be cross-compiled, but looking at that config now, maybe it's not strictly necessary on macOS.

Maybe that's the source of the confusion on my pulls, as non-Darwin platforms do not have pre-compiled Swift runtime "hostlibs" for all supported arches installed. I'll update my comment above.

Yes, that's correct. We'd like to get to the point where we can remove the existing (C++) parser and driver in favor of the Swift implementations, which means it will no longer be possible to build a Swift compiler without a Swift compiler.

Right.

I think there's some confusion about how cross-compilation should work going forward. Everything in SwiftCompilerSources is based on custom commands for building Swift code, because it predated having sufficient Swift support in CMake. That also meant that it needed its own bespoke support for cross-compilation.

Now, we have improved support for Swift-in-CMake, which is how the swift-syntax-based parts of the compiler (e.g., ASTGen and macros support) are built. SwiftCompilerSources will be moving over to this approach as well, because it greatly simplifies the CMake build and lets us use the normal CMake feature set vs. having our own hand-rolled versions. For cross-compilation, I believe this means we should be using toolchain files consistently, not continuing with our own BOOTSTRAPPING configuration. The end result should, I think, make it easier to cross-compile because CMake is handling it for us.

Yes, I would like us to get to the point where SwiftCompilerSources is building with Swift-in-CMake (rather than the bootstrapping code) before the 6.0 branch. Aside from the technical reasons above, this goal is partly motivated by Windows, where the bootstrapping code isn't working so certain optimization passes are disabled. All of ASTGen + macros are building fine on Windows with the Swift-in-CMake approach, and it's where we want to end up anyway, so I'd like us to cut over to it so we have a consistent set of compiler features and optimization passes on all of the host platforms.

Now, with the Swift 6.0 branch it will still be possible to build a working---albeit somewhat limited---Swift compiler without having a Swift host compiler beforehand. Beyond the Swift 6.0 branch, we won't promise that any more for main.

I expect there will, but not through the BOOTSTRAPPING code. However, I don't have a solid handle on exactly what needs to be done to make the Swift-in-CMake approach cross-compile easily, and will have to defer to others (e.g., @etcwilde, @bnbarham, @compnerd).

Doug

7 Likes

Thanks for the detailed response: I didn't know that you want to adopt Swift-in-CMake more, which I think would require updating to a much newer version of CMake than the project currently requires.

I'll wait and see how that new CMake work by others goes, then chip in any changes I need.

swift-syntax currently defines the following compatibility policy:

We require that swift-syntax builds with the latest released compiler and the previous major version

Is it planned to have this, or something similar, be the official policy for all Swift compiler sources going forward? SwiftCompilerSources/README.md is currently documented as only requiring 5.5 for hosttools mode, which I assume won't be the case for much longer.

I’ve started migrating the runtime/stdlib build over locally. I currently have most of the main runtimes building (swift core, concurrency, distributed, differentiation, etc). I’ve successfully cross compiled those libraries from my Mac to iOS, Arch Linux, Oracle Linux 5 and run some simple programs (async Fibonacci, printing type names and command line arguments, and some simple linked lists) also cross-compiled to these systems. I still need to try building for windows and android. Windows is hard because it’s not exactly something I can cross compile to from a Mac legally.

In order to cross compile, you’ll need the sysroot, a cmake cache file with what functionality you want enabled, and a tool chain file specifying the compilers and such that you want to use.

One thing to note, like LLVM’s runtime builds and unlike Rust builds, we’ve decided that the compiler should be the thing that can build and run with older runtimes, while the stdlib can use new features that just landed in the compiler. For building them independently, this means that you will need a compiler that is at least as old as the stdlib to build the stdlib.

These two properties have an interesting interaction with the android support in CMake, which knows how to reach into the NDK and pull out the tools from the NDK itself so you don’t need to pass a toolchain file. The NDK toolchain is obviously missing a certain swift compiler and won’t have a clang with swift calling convention support, so I still need to make sure that we can override the toolchain pieces when working with the android support. Otherwise, it should be fairly straightforward to support android as well.

CMake 3.26 is the oldest version that has sufficient support for all of the pieces that we need (targets that can mix C, C++, and Swift being the main one). I am, however, enabling the new features coming in 3.29 in a backward compatible way, so if you have a newer CMake, you can use LSP support in the Swift portions of the project as well.

That seems to be within the policy recommended above and sounds completely reasonable to me.

I thought it needed at least 5.8 or 5.9 for the C++ interop support these days?

2 Likes

Good point; so I suspect that README is already likely out of date.

Alright, I didn't know you were working on this, and you never mentioned it when commenting on my cross-compilation pull.

Let me know if you run into any obstacles with that. The stdlib could be built for Android without any patches for awhile now, but the latest NDK 26 requires a small patch that I have submitted.

The current minimum is 3.19.6, which is what 5.9 uses, but 5.10 was bumped to using 3.24.2. Obviously, trunk will require another bump to use CMake's Swift support.

Yeah, I was trying to keep it somewhat quiet until I was confident that we have the necessary support in CMake before making any announcements. Cat's out of the bag now. :slight_smile: There are plenty of things that still need doing, so once I'm comfortable with where things are, I'm happy to put the branch up publicly and take assistance in hooking up the rest.

Sure. I don't think it should be too bad. Looking at the Android toolchain file, it looks like it's mostly just manipulation of the CMAKE_C_COMPILER and CMAKE_CXX_COMPILER:

if(ANDROID_CCACHE)
	set(CMAKE_C_COMPILER        "${ANDROID_CCACHE}"       CACHE FILEPATH "ccache")
	set(CMAKE_CXX_COMPILER      "${ANDROID_CCACHE}"       CACHE FILEPATH "ccache")
	set(CMAKE_C_COMPILER_ARG1   "${ANDROID_C_COMPILER}"   CACHE FILEPATH "C compiler")
	set(CMAKE_CXX_COMPILER_ARG1 "${ANDROID_CXX_COMPILER}" CACHE FILEPATH "C++ compiler")
else()
	set(CMAKE_C_COMPILER        "${ANDROID_C_COMPILER}"   CACHE FILEPATH "C compiler")
	set(CMAKE_CXX_COMPILER      "${ANDROID_CXX_COMPILER}" CACHE FILEPATH "C++ compiler")
endif()

If we insert our clang into the cmake cache under CMAKE_C_COMPILER and CMAKE_CXX_COMPILER before the toolchain file executes, the first one wins, so we would get precedence. We can also set environment variables for CC, CXX, and SWIFTC if we want as well, so we have options.

I can't say I love the implementation of their toolchain file here. It would be better to consistently set the compiler in CMAKE_<lang>_COMPILER and then use CMAKE_<lang>_COMPILER_LAUNCHER to pass ccache, but I can't throw rocks at Google while the Swift build does the things it does either. ¯\_(ツ)_/¯

Yep, I'm able to go back as far as 3.26, so it will need bumping. Luckily, CMake is pretty easy to build.

All you had to say is that you were working on more comprehensive support for cross-compilation, without giving any details, and I would have deferred to you.

Sounds good.

Note that the CMake build currently uses the native prebuilt host clang specified by CC/CXX to build the Swift compiler and the freshly-built Swift-forked clang to build the Swift stdlib, unless you pass in a build flag to build both with the prebuilt host clang.

I don't know how well CMake's built-in cross-compilation supports such mixing of different C++ compilers for different parts of the build, but it is an issue worth watching out for.

That’s actually one of the big issues with the runtime build today. CMake does not handle this well architecturally; it runs a bunch of compiler checks when you enable the language with the compiler stored in CMAKE_<lang>_COMPILER which are then saved in the cache. If you change the language compiler after the language was enabled, all of the cached values are still floating around from the old compiler and may or may not be relevant for the new one.

I’m splitting the projects so that the stdlib can be built entirely separately, and then we can either use ExternalProject_Add from within the compiler build and invoke a child cmake invocation at build time for each runtime target (like LLVM’s LLVM_ENABLE_RUNTIME support), or left as an exercise for the build-orchestration tool of choice.

2 Likes