https://briancallahan.net/blog/20250112.html
Dr. Brian Robert Callahan
academic, developer, with an eye towards a brighter techno-social
life
---------------------------------------------------------------------
Home | Blog archives | LinkedIn | CV | Code | Extras
---------------------------------------------------------------------
[prev]
[next]
2025-01-12
Let's port the GNAT Ada compiler to macOS/aarch64
After getting a port of GDC working on my new MacBook Pro, there are
still two languages left in the GCC suite that I don't have: Ada and
Go. Some searching around makes it seem pretty clear that Gccgo is
not yet really on the table to macOS. But there should not be any
reason we can't add Ada to our GCC suite, seeing as there is already
support for macos/aarch64 in the repository. I wanted to get a macOS/
aarch64 native Ada through the GNAT compiler in GCC.
But try as I might, I could not find any precompiled packages for it.
I guess part of the issue is that macOS/aarch64 support is not fully
upstreamed into GCC propre; instead, Iain Sandoe has a GitHub
repository that includes the necessary changes for full support. I
used his gcc-14-branch repository to build GDC.
So let's get to work.
Virtualizing macOS
I don't have Rosetta on my machine. I know it takes all of two
seconds to install it, but I also wanted an excuse to play around
with Apple's virtualization framework. After some googling around, I
settled on using VirtualBuddy as my virtualization manager. It made
installing a virtualized copy of Sequoia 15.2 incredibly easy; just
say you want to install from the Internet, choose the version you
want to install, and away you go. I gave my virtualized machine 4
CPUs and 16 GB of RAM and a 128 GB hard disk, and it felt just as
snappy as the host (the host is a MacBook Pro M4 Max with 64 GB of
RAM). I installed the command line tools, Rosetta, and some Homebrew
packages on this virtualized machine and I was ready to go.
Step 1: Finding a binary of GNAT
So why did I install Rosetta? My suspicion was that someone had a
binary package of GNAT for macOS/x86_64. But I looked in the usual
places: Homebrew, pkgsrc, MacPorts, and Fink, and none of them had a
package. I was beginning to wonder if our project was going to end
before it began. Because GNAT is written in Ada, you need an Ada
compiler to compile it.
I eventually stumbled upon Alire which bills itself as Ada's version
of Rust's cargo or OCaml's opam. The Alire homepage directly states
that they have a recent Ada package for macOS/x86_64. This was our
way in. I followed the tutorial to get the alr binary installed on
the virtual machine and got a copy of the GNAT compiler, which
happened to be version 14.2.0. That's great, because that's the same
version we built with our GDC compiler, and what we will be building
from the gcc-14-branch repository.
The compiler was built for a x86_64-apple-darwin21.6.0 target. We
will use this to get to a aarch64-apple-darwin24.2.0 target.
Step 2: Building a Stage 1 GNAT cross compiler
I added this Alire-provided GNAT to my PATH and set up the
gcc-14-branch repository to prepare to build. Make sure it goes at
the front of your PATH so that clang doesn't get picked up instead. I
did run the contrib/download_prerequisites script so that GMP, MPFR,
MPC, and ISL would be built statically into GCC; that way I don't
have to deal with any shared libraries and they're not that big so it
won't take long to build them.
This first pass we need to build a compiler that is built
(effectively) from macOS/x86_64, will run on (effectively) macOS/
x86_64, but produces code that runs on macOS/aarch64. We also need to
remember that all the build tools understand both x86_64 and aarch64;
that will be a big help for us. To start, we can run a configure
invocation that looks like this:
env OTOOL=otool AS=as AR=ar RANLIB=ranlib LIPO=lipo NM=nm DSYMUTIL=dsymutil ../gcc-14-branch-gcc-14.2-darwin-r2/configure --prefix=/opt/gcc14 --enable-languages=ada,c,c++ --with-sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX15.sdk --build=x86_64-apple-darwin21.6.0 --host=x86_64-apple-darwin21.6.0 --target=aarch64-apple-darwin24.2.0 --disable-nls --with-ld=/usr/bin/ld --with-as=/usr/bin/as
Then we can run our build with:
gmake V=1 -j4
I installed GNU make from Homebrew since that is much newer than what
I got from the command line tools.
For now, I am only going to worry about getting GNAT up and running;
once we have it then I can worry about combining GNAT and GDC
together into one suite.
I did encounter a few build failures but these all seemed to revolve
around autotools wanting prefixed versions of the tools; I would just
edit Makefiles and re-run gmake when that happened. Eventually it did
succeed and so I installed it into a fake directory, tarred it up,
and then installed it on the virtual machine.
Step 3: Building a Stage 2 GNAT native compiler
Now we can use this new compiler to build another new compiler. This
new compiler will be built with the compiler we just built: because
the compiler we just built produces code for
aarch64-apple-darwin24.2.0, even though it runs on
x86_64-apple-darwin21.6.0, once it is finished compiling GCC, that
GCC will be a native macOS/aarch64 compiler.
We'll need to reset our PATH to get rid of the Alire-provided
compiler. We don't need it any more. We do need to add the compiler
we just built to our PATH as that will be used to create our native
compiler.
Let's create a new build directory and run our configure invocation
as follows:
env OTOOL=otool AS=as AR=ar RANLIB=ranlib LIPO=lipo NM=nm DSYMUTIL=dsymutil ../gcc-14-branch-gcc-14.2-darwin-r2/configure --prefix=/opt/gcc14 --enable-languages=ada,c,c++ --with-sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX15.sdk --build=x86_64-apple-darwin21.6.0 --host=aarch64-apple-darwin24.2.0 --target=aarch64-apple-darwin24.2.0 --disable-nls --with-ld=/usr/bin/ld --with-as=/usr/bin/as
So what we are saying here is that our build machine is (effectively)
macOS/x86_64 but we would like to build a compiler that runs on macOS
/aarch64 and produces code for macOS/aarch64. A compiler that
produces code for the platform it runs on is a native compiler.
This failed very quickly. Turns out the stage 1 collect2 binary was
miscompiled and segfaults when it is used. What I did to overcome
this was copy the native collect2 binary from my GDC compiler to the
GNAT compiler. Everything was happy after that.
Now, we can compile with:
gmake V=1 -j4
Like with the stage 1 compiler, there are some intermittent build
failures. But none of these are catastrophic; it is a matter of just
editing the Makefiles in question and re-running gmake. The most
difficult error was that when linking libstdc++.dylib, there was a
linker error that you were linking the library with a library of the
same name. The problem is that the infrastructure incorrectly wanted
to link with g++ instead of gcc. There is a comment in the right
place in the Makefile so it's relatively clear that you just need to
replace g++ with gcc and it will work. I've never had that problem
before so perhaps it is something with cross compiling.
Eventually it will complete successfully, and then same thing: I
installed to a fake directory, removed the stage 1 compiler, then
tarred and installed the stage 2 compiler.
Step 4: Putting it all together
Now that I have a native GNAT for macOS/aarch64, I want one single
GCC package that will have all the languages that are available: Ada,
C, C++, D, Fortran, Modula-2, Objective-C, and Objective-C++. I moved
the tarball of the stage 2 GNAT compiler to my host machine, shutdown
the virtual machine, and installed the stage 2 compiler. I then put
the stage 2 compiler in front of the GDC compiler in my PATH and
built the gcc-14-branch compiler one last time:
../gcc-14-branch-gcc-14.2-darwin-r2/configure --prefix=/opt/gcc14 --enable-languages=ada,c,c++,d,fortran,m2,objc,obj-c++ --with-sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX15.sdk
That's all we need now. All of build, host, and target are
aarch64-apple-darwin24.2.0, a fully native compiler. And GCC will
detect that automatically and create a new native compiler.
One last time to build GCC, but let's go a little faster now that we
have more CPU cores:
gmake V=1 -j10
This built without any problems. Then for one last time, install to a
fake directory, tar it up, remove the old stage 2 compiler and old
GDC compiler, and install this new complete GCC suite.
Conclusion
I've uploaded the tarball here for those that want it. You will need
to have the command line tools installed. If someone knows how to get
GCC to autoselect between Xcode and the command line tools, please
let me know. You'll also need to put /opt/gcc14/bin at the front of
your PATH to use it.
Even better, now no one will ever have to go through the hassle of
bringing up Ada support on macOS/aarch64 again. We now have a
compiler that can be used as a bootstrap for future releases of GCC.
Maybe we'll even have some package managers provide GNAT compilers
since the hard work has been done.
Top
RSS
---------------------------------------------------------------------
OpenBSD httpd