Dr. Brian Robert Callahan

academic, developer, with an eye towards a brighter techno-social life



[prev]
[next]

2025-08-16
Despite thoughts to the contrary, GNAT (Ada) is in fact fully supported on illumos

TL;DR: Get the binaries here.

I am building a little script to automagically bootstrap GCC with the two difficult to build languages: Ada and D. They are difficult to build chiefly because they are written in themselves, so you need a working GNAT to build Ada and a working GDC to build D.

After getting the script to work on macOS, both arm64 and amd64, I turned my attention to other operating systems. There are of course the *BSDs, which should be no more difficult than macOS. And then there is illumos, the continuation of OpenSolaris. That seemed like a good next target.

GNAT and illumos, unknown together

According to this webpage, illumos "currently has no Ada support within GNAT." That seemed a little odd to me. As we previously learned, illumos does run Solaris 10 binaries without issue. Surely at some point in time, GNAT was being built for Solaris 10 or some earlier version. I would find it hard to believe otherwise.

Indeed, I found a version of GNAT claiming to be "4.9.0 20140422" and even someone who claimed to use the bootstrap to get GNAT 7.5.0 on illumos. Seems like we're on the right path.

I did end up downloading that bootstrap and running it on the latest OpenIndiana and it did run. But it proved too difficult to actually compile anything with it; as far as I can tell, it looks like there may have been changes in Sun ld in the intervening 11 years because this bootstrap struggled to link things.

We need a new approach.

Cross-building GNAT, stage1

A better approach will be to cross-build GNAT (and GDC) for illumos from a system that already has a working copy. For least headache, choose macOS or Linux; both are known to have working the most recent GNAT and GDC.

We will need to complete a cross-build pipeline that looks something like this:

  1. Get headers and libraries from the target operating system, illumos in this case
  2. Build a cross-binutils where build and host are both set to your macOS or Linux machine, and target is your target machine (x86_64-pc-solaris2.11, in this example), so you have a working assembler, linker, and support utilities
  3. Build a cross-gcc where build and host are both set to your macOS or Linux machine, and target is your target machine
  4. Build a second binutils using the cross-gcc where host and target are both set to your target machine
  5. Build a second gcc using the first cross-gcc where host and target are both set to your target machine

At the end of this pipeline, we will have a complete gcc and binutils suite that was built on your macOS or Linux machine, but runs on and generates code for the illumos machine. Pretty neat. I know that LLVM really solved the problem of cross-compilation more easily by having all their tools always understand all their targets, but the GNU approach I think people think it's more scary than it really is. And we have a lot of thanking the GNU people here—so many of our favorite platforms were brought up using some method similar to this, increasing the capabilities of the free software compilation stack in a time where there really was only one option in the space.

Get headers and libraries

You need headers (all those files in /usr/include) and libraries (all those files in /lib, /usr/lib, and other places) from the target machine on the build machine. I ended up using the headers and libraries from Tribblix, a one-man illumos distro. It is incredibly minimal; we don't need all the headers and libraries you get from a full desktop environment like I have on OpenIndiana.

As an aside, the Tribblix installation is nearly as good as Haiku: all I had to do to install Tribblix was run a single script, ./live_install -G c1d0 server develop and reboot and I was done. Took all of a minute on a relatively weak QEMU VM. I chose server and develop during installation as that seemed like that most minimal usable system that would have all the headers and libraries needed. Put those in a tarball and sftp'd it to the build machine, and we're done with Tribblix.

On the build machine, I untarred them to ~/solaris/usr, giving me a ~/solaris/usr/include and ~/solaris/usr/lib that had everything we'll need to complete our cross-build pipeline.

Build the first cross-binutils

The latest version as of this blog post is 2.45. Download it, extract it, run:

./configure --target=x86_64-pc-solaris2.11 --prefix=~/solaris/stage1/usr --disable-werror --disable-nls && gmake V=1 -j16 && gmake install

And wait a whole 30 seconds for my build machine to build it (so very appreciative of my super fast hardware purchase last year).

Build the first cross-gcc

Now things start to get a little tricky. First, let's add our new cross-binutils to our PATH:

$ export PATH=~/solaris/usr/bin:$PATH

Now, we need to make sure we cross-build the exact same version of GCC as what we are running on our build system. There is no need to get fancy here and try to move versions; we can do that on the target machine later. Right now, I have GCC 14.2.0 on the build machine, so that is the version I am going to cross build. Let's download and extract it.

Then, to make our lives easier, we are going to build all the GCC dependencies (gmp, mpfr, mpc, and isl) within the GCC build itself. Yes, that means it will technically take a little longer. But it also means that we don't need to get fancy with building libraries outside the GCC build process itself. Everything will be taken care of for us. It also makes the resulting GCC suite a lot more portable, as all the dependencies will be statically linked into the binaries. We won't need to worry about moving the binaries from machine to machine and getting spurious errors because a machine happens to have a different, incompatible, version of one of the dependency libraries. GCC will always just work.

We can do this by cd'ing into the newly created gcc-14.2.0 directory and running:

$ ./contrib/download_prerequisites

You should then rm the gettext directory this creates; I found that GNU gettext and Solaris gettext have subtly different function signatures, and this breaks the GCC build if GCC tries to build its internal copy of gettext (and we don't need gettext for this exercise). On every other system, I leave the gettext directory and even configure GCC with --with-included-gettext, again to ensure maximum compatibility when moving binaries to unknown machines. illumos just happens to be different in this one regard.

I prefer to build GCC out-of-tree, so assuming our GCC source directory is ~/gcc-14.2.0, let do this:

$ cd && mkdir build && cd build

Now we can configure the build of our first cross-gcc:

$ ../gcc-14.2.0/configure --with-sysroot=~/solaris --prefix=~/solaris/stage1/usr --enable-languages=ada,c,c++,d --target=x86_64-pc-solaris2.11 --with-gnu-as --with-as=~/solaris/stage1/usr/bin/x86_64-pc-solaris2.11-as --with-gnu-ld --with-ld=~/solaris/stage1/usr/bin/x86_64-pc-solaris2.11-ld --disable-nls

The --sysroot option tells GCC that it should consider ~/solaris the root for where our headers and libraries are. That is to say, this option forces GCC to consider ~/solaris/usr/include the location of our headers and ~/solaris/usr/lib the location of our libraries. We need to ensure this GCC finds our target's headers, not our build machine's headers.

You do need the --with-gnu-as and --with-gnu-ld flags for Solaris and illumos, otherwise GCC assumes you are using the Sun assembler and linker but we are using the GNU assembler and linker. The Sun assembler isn't even free software. The Sun linker is, but I think it's only buildable on Solaris and illumos without herculean effort. The GNU linker works just fine.

Now we can let it build:

$ gmake V=1 -j16

This went off without issue; now we can install with:

$ gmake install

And that gives us a complete cross-binutils and cross-gcc suite installed on our machine. We could use it to build any arbitrary code for illumos if we wanted. But we have a specific goal in mind: a native binutils and gcc for illumos.

Build the second binutils

Technically, this binutils will only be cross from the perspective of the build machine. From the perspective of the target machine, it is a native binutils—it creates binaries for the same platform it runs on. But, of course, we built it on a different platform.

I didn't build binutils out-of-tree, so let's rm the binutils directory and re-untar the tarball to get a fresh copy of the binutils source. The configure invocation is mostly the same but with the addition of specifying the host:

$ ./configure --host=x86_64-pc-solaris2.11 --target=x86_64-pc-solaris2.11 --prefix=/opt/gnu --disable-werror --disable-nls && gmake V=1 -j16 && gmake DESTDIR=~/solaris/fake install

We also changed the prefix to be the real home for our tools. I chose /opt/gnu because I know that won't interfere with anything in OpenIndiana or Tribblix, and I don't think it would interfere with any default install of any illumos distro, so it's a safe location.

Because we can't actually run this binutils on our build machine, it doesn't make much sense to me to actually install it to its true home. Let's install it to a fake directory for now and at the end we can tar it all up and drop it on the target machine for real installation.

Build the second gcc

Almost done with our build machine. If we can get this second gcc built, then we can finally bootstrap a complete 3-stage native bootstrap on our target machine (I wouldn't trust this gcc to have everything perfectly correct). One last configure invocation:

$ ../gcc-14.2.0/configure --prefix=/opt/gnu --enable-languages=ada,c,c++,d --host=x86_64-pc-solaris2.11 --target=x86_64-pc-solaris2.11 --with-gnu-as --with-as=/opt/gnu/bin/as --with-gnu-ld --with-ld=/opt/gnu/bin/ld --disable-nls

The build needed a build system executable as and ld in /opt/gnu/bin. Not sure why but I didn't argue I just copied the stage1 binaries there. We're not installing this gcc to its real location anyway. Then there were some build errors in the C++ headers. Maybe it was a subtle misdetection between the build machine and the target machine. The good news is that all the offending code was encased in #if preprocessor blocks and simply setting those blocks to #if 0 fixed the build errors.

The offending files are x86_64-pc-solaris2.11/libstdc++-v3/include/complex and x86_64-pc-solaris2.11/libstdc++-v3/include/tr1/special_function_util.h.

Finally, the build failed saying it needed a copy of the headers in a specific location. I just copied the headers in ~/solaris/usr/include to where GCC said it wanted them to live and then all was fine.

For some reason, the cc1plus binary wants to link with -lxnet but it is not automatically added, so the build errors out there. All you need to do is add -lxnet after -lzstd in the linker invocation and then you can continue on.

Finally, the g++-mapper-server utility wants to link with -lxnet -lsocket -lnsl -lmp -lmd but that is not automatically added. Same thing: just manually link adding it in and then you can continue on.

And that's it. The build should complete now. Let's install it to the fake directory:

$ gmake DESTDIR=~/solaris/fake install

Now we can tar up our finished fake directory:

$ cd ~/solaris/fake && gtar -cz -f gcc14-illumos.tgz opt/

It was somewhat large, about 1.2 GB. Probably because none of the binaries are stripped. Now we can sftp this tarball to our OpenIndiana machine and do a full native 3-stage bootstrap.

A fully native compiler build

Now on the OpenIndiana machine, we can untar our bootstrap gcc with:

$ sudo gtar xf gcc14-illumos.tgz -C /

Let's add the new directory to our PATH:

$ export PATH=/opt/gnu/bin:$PATH

Moment of truth: run gcc --version and see if we get output. And we do!

I wrote a simple hello world in Ada and was able to successfully compile it. Awesome, GNAT does work on illumos.

Now you can build a complete native 3-stage bootstrap of whatever version of GCC you want, with whatever flags you want.

Or if you just want mine, I used the bootstrap compiler to build a complete GCC 15.2.0 with Ada, C, C++, D, Fortan, Modula-2, Objective-C, and Objective-C++ support. I believe Go is supported on illumos too but I get a build error when building the 32-bit Go libraries, and fixing that is out of scope for this project. So we'll have to do without it for now.

You can get my GCC here.

To install, just extact the tarball like so:

$ sudo gtar xf illumos-x64.tar.gz -C /

Note that the -lxnet and -lxnet -lsocket -lnsl -lmp -lmd fixes are still needed even with a fully native build. If you build GCC 15.2.0 via my GCC autobootstrapper it will apply these patches for you.

Of course, you could just use the binaries directly if you'd like; there is no real reason to rebuild GCC unless you want to.

Because I noticed there was a significant difference in the binutils between OpenIndiana (2.44) and Tribblix (2.39), I assume that means that each illumos distro is responsible for maintaining their own toolchains. We would need to be careful if using a distro-installed binutils; it is theoretically possible that during the GCC build, GCC would detect and use features from a newer binutils that works fine on one distro but doesn't work at all on another because of an older binutils. I made the decision to ship my GCC with binutils (2.45) also installed in /opt/gnu and GCC configured to use that binutils; this avoids the issue entirely. And as an added benefit, you don't need your distro's GCC or Binutils installed if you don't want.

Testing

We should test our fully native GNAT since that will tell us how much support, if any, actually exists and gives us clues as to where to begin adding support. I followed the directions provided by GCC; OpenIndiana had all the needed packages in their package repository. I also discovered I could use the gmake check-ada target to test only GNAT, which is good because that saves a lot of time and right now this is just about learning more about what support is missing for illumos in GNAT.

SunOS openindiana 5.11 illumos-5e96687e18 i86pc i386 i86pc

GNU Make 4.4.1
DejaGnu  1.6.3
Expect   5.45.4
Tcl      8.6

Native configuration: x86_64-pc-solaris2.11

LAST_UPDATED: Obtained from git: releases/gcc-15.2.0 revision 5115c7e447fc07457443df874bf57840e8316d5f

		=== acats tests ===

		=== acats Summary ===
# of expected passes		2328
# of unexpected failures	0
Native configuration is x86_64-pc-solaris2.11

		=== gnat tests ===


Running target unix

		=== gnat Summary ===

# of expected passes		3545
# of expected failures		25
# of unsupported tests		11
/export/home/brian/build/gcc/gnatmake version 15.2.0


Compiler version: 15.2.0 
Platform: x86_64-pc-solaris2.11
configure flags: --prefix=/opt/gnu --enable-languages=ada,c,c++,d,fortran,m2,objc,obj-c++ --host=x86_64-pc-solaris2.11 --target=x86_64-pc-solaris2.11 --with-gnu-as --with-as=/opt/gnu/bin/as --with-gnu-ld --with-ld=/opt/gnu/bin/ld --with-build-time-tools=/opt/gnu/bin --with-build-config=no --enable-shared --enable-plugins --enable-__cxa_atexit --enable-initfini-array --with-diagnostics-urls=auto-if-env --enable-default-pie --enable-default-ssp enable_frame_pointer=yes

So... uh... illumos has full Ada support within GNAT.

Conclusion

We proved that GNAT is in fact fully supported on illumos. Maybe it was just a case of no one tried it. We tried it, and we learned something important. I'll let the ada-lang.io people know of our success.

We also learned how to cross build gcc for any platform we want. So if we want to build GNAT or GDC on a platform that doesn't already have it, and should support it, we can do it ourselves. Very neat.

Oh, and building GDC went without a single issue. But I somewhat already knew that was going to be the case, as I previously ported DMD to illumos.

Top

RSS