RFC: Revitalization of the x86_gcc2 ecosystem

Context

It is well known that Haiku on 32-bit x86 uses the legacy GCC2 ABI for compatibility with BeOS applications. To build binaries with this ABI, we rely on GCC 2.95, a legacy compiler that does not work on modern hosts (e.g. x86_64-linux) and sometimes suffers from bitrot.

Furthermore, the compiler does not support modern versions of C/C++. To have newer apps ported to x86, Haiku currently requires a “hybrid” - a build with duplicate system libraries, one with the legacy GCC2 ABI, one with the modern ABI built by newer compilers. This hybrid setup introduces complexity both when building software and when using it.

A recent attempt has been made to patch the LLVM toolchain to generate GCC2-compatible binaries. The port has proven success with working GCC2 ABI binaries on both Linux and Haiku x86_gcc2.

There has been a proposal to allow gradually upstreaming these changes to the LLVM project. While there is no fundamental objection and efforts have been made to resolve technical concerns, the Clang maintainers want evidence of support from the Haiku community to justify the addition of this legacy ABI to the codebase:

The Clang Area Team did discuss it very briefly and our biggest concern boils down to “is there enough of a need to warrant supporting it forever?” kinds of questions. For example, if you can point to discussions within the Haiku community that they need this support and are bought into the idea, that would help strengthen the RFC.

(Source for those who can access the LLVM Discord).

If Clang added support for producing x86_gcc2 binaries, would you, the Haiku community, adopt it?

Benefits include:

  • No more setarch x86 to use tools.
  • No more $secondaryArchSuffix in recipes.
  • No more split dependencies for x86_gcc2.
  • Fewer downstream patches for haiku/buildtools.
My adoption plan

Stage 1: HaikuPorts

Step 1: Existing Package

Once the RFC to LLVM is accepted, the GCC2 ABI backend can be added to HaikuPorts. It will be built into the existing llvm<version>_clang_x86 package and made available to Haiku users while upstreaming work is taking place.

Step 2: New Ports

The new llvm_clang_x86 package can then be used as a build dependency. We can use that to build ports that were previously broken/unsupported on x86_gcc2 - including a native llvm_clang port for GCC2.

Step 3: Unified Ports

With the new x86_gcc2 ecosystem stabilizing, we can start unifying the ports under clang and removing secondary architecture workarounds.

Stage 2: Haiku

Step 0: Misc. Fixes

This stage will happen alongside Stage 1. There will be no fundamental changes on how Haiku is built. However, we will make small changes that facilitates the porting of modern binaries to the x86_gcc2 environment. Examples of such changes include:

Step 1: Build Packages

Once proven usable in the HaikuPorts ecosystem, Clang-built packages can be used as dependencies for x86_gcc2 system libraries/apps. The apps themselves will still use x86_gcc2.

Step 2: Side-by-Side Configuration

Once Clang is more stable, we can experiment with building using the LLVM toolchain. Some configuration to the Jam build system may be needed.

Step 3: Migration

Once the system can be built using Clang with no issues, we can migrate to Clang as the default compiler. Once this is achieved, we will only require one toolchain for x86.

The GCC 2.95 port can still be maintained in HaikuPorts for building legacy non-conforming C++ codebases.

It would be really nice if you could comment your thoughts on this. If you like support this idea but don’t know what to say, a :heart: would be greatly appreciated!

10 Likes

Would be nice if some key Haiku community members can provide their thoughts:

Thanks a lot!

I don’t think we could manage to get away with this since the C++ libraries between GCC2 and GCC13 are not compatible and can’t be made compatible. We can get a lot more C programs on the GCC2 ABI this way, sure, and anything in C++ that can run with only C++98 will be fine, but that’s still going to exclude a lot of software.

1 Like

I will voice a very unpopular opinion. BeOS R5 was my daily driver and I loved it. That being said, Haiku is leaps and bounds better than BeOS. I know maintaining BeOS compatibility was a R1 goal, which was logical in 2005, but the Haiku developers have already surpassed BeOS and it’s time to move on.

4 Likes

Could you please elaborate more on this?

I agree that they are not compatible. For example, we cannot pass a modern std::__v1::string from libc++ to a libstdc++ 2.95 std::string.

That said, we can make them co-exist peacefully in the same address space by pointing to the same C++ runtime infrastructure provided by libgcc/libroot and having versioned symbols (versioning namespaces on the C++ side, and potentially additional versioning on the linker scripts).

This is how the GCC2 port of the whole LLVM toolchain on x86_gcc2 is achieved. It uses a modern C++26 libc++.so that explicitly takes a dependency on libstdc++ from GCC2.

Since most Haiku/Be APIs are independent from std:: elements and use their own classes (e.g. BString), ports can depend on libc++.so for C++ features while consuming libbe.so APIs at the same time. System binaries can continue to depend on libstdc++ internally.

1 Like

I think this is how we can move on and modernize the internals of Haiku without leaving BeOS apps behind.

1 Like

I do love the effort you’re putting into this!

3 Likes

We have essentially moved on from BeOS at least on 64-bit version which is the most popular version. Haiku devs are committed to keeping Be binary compatibility support until R1 which is still a relevant goal even today. Any Be binary compatibility tweak however is denied because the devs don’t really care about binary compatibility past the surface level of API support.

Once R1 is released both Be and Haiku apps will have a stable release going forwards. You’re essentially saying that Haiku is ready for R1 now but unfortunately it isn’t quite yet.

I won’t comment on the clang changes.

1 Like

But libbe.so links to libstdc++.so at least for some internals. And we can’t have the new libstdc++ and the old libstdc++ in the same ELF namespace as we’ll get symbol clashes, right?

I would advocate for LLVM’s own libc++ if we need an alternative C++ library for ports.

As for the new libstdc++, there will also need to be similar work to wire it to the old libgcc runtime for it to co-exist.

Regarding the original question, I believe, yes, we can have that. The old libstdc++ is actually libstdc++.r4.so. So, if libbe.so requires vector from libstdc++.r4.so and foo requires std::__cxx11::vector from libstdc++.so, they will resolve just fine.

Dependency dump of some binaries from the LLVM GCC2 port
$ readelf -d libc++.so.1.0

Dynamic section at offset 0x1269b0 contains 27 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libroot.so]
 0x00000001 (NEEDED)                     Shared library: [libstdc++.r4.so]
 0x0000000e (SONAME)                     Library soname: [libc++.so.1]
$ ./llvm-readelf -d ./llvm-cat
Dynamic section at offset 0xae9484 contains 27 entries:
  Tag        Type           Name/Value
  0x00000001 (NEEDED)       Shared library: [libc++.so.1]
  0x00000001 (NEEDED)       Shared library: [libroot.so]
  0x00000001 (NEEDED)       Shared library: [libbsd.so]
  0x00000001 (NEEDED)       Shared library: [libnetwork.so]
  0x0000001d (RUNPATH)      Library runpath: [$ORIGIN/../lib]

Don’t think I have any useful information to add from my side, it’s a “tad” over my skills or level of understanding not having used clang builds.

For the most parts when doing some ports in 32bit it’s mostly about checking builds and most of them already target x86 there, only when it’s really required I check with the old compiler.

1 Like

That is alright!

The end goal of this is making things simpler so that port maintainers like you will not even need to know about the concept of an old compiler anymore. All Haiku versions will use the same toolchain and things will just magically work. How does that sound?

2 Likes

I personally don’t have a problem with the way it works now (been around long enough for that :smiley: ), but from the point of new contributors I can see that it would improve/ease the process of creating recipes.

I only think about recipes that support both compilers now on 32bit, providing packages for both arch’s (bison comes to mind here), how will the gcc2 packages be maintained?

1 Like

Right now, many dual x86_gcc2/x86 packages have the GCC2 version outdated. This would be quite confusing to users, having to setarch x86 to get the results they want. See this example where I have to setarch x86 for the strings command to use the updated version and show the output I expected.

My vision would be, the secondary architecture package will be deprecated and removed, while the native x86_gcc2 package will be updated to the latest version, powered by the new Clang toolchain if necessary.

Importing llvm-clang as a build dependency and setting CC=clang CXX=clang++ will make things work on all architectures.

As for the GCC 2.95 package itself, my proposal would be:

What do you think? I think this will improve the experience for porters and users alike.

1 Like

Correct there, would be nice if those could be updated to use the new compiler, but I’m not that sure always which parts are still required inside Haiku (build) or not, so where needed those are still in place.

This is where it gets over my head :slight_smile: if secondary architecture is dropped, how will you build with GCC2 then? And how will you know (when switched), which package is build with clang or gcc when both are “primary” arch builds?

1 Like

You can use GCC2 as the build dependency like it is right now instead of Clang, if GCC2 is absolutely needed (e.g. for very old codebases).

GCC2 can still bootstrap itself and produce another GCC2 build, if needed.

When the Clang port is stable, everything will just work and users will not need to know what the package is built with. This would just be like an upgrade of GCC 11 to GCC 13 - we usually do not care which version of GCC built the package.

Of course, it will take time to prove that the Clang port is mature enough for the job. That is why in my adoption plan, I suggested only starting with packages not available to GCC2:

1 Like

This is also a bit strange and maybe would need a change at haikuporter? At this moment you can’t use a _x86 package for primary architecture (eg, can’t use Qt for primary), how would 32bit know it needs the llvm_clang_x86 when building for primary?

I would rather let the gcc2 ABI retire and have a peaceful death. I hoped no one would maintain it, no one would really care at some point, and we would just remove it and move on.

Looks like we could be dealing with it for a few more years now. That doesn’t make me happier.

Anyway, it will be broken by the year 2038 bug (all the x86 32 bit support will), so, if you want to keep it on life support for these 12 years, have fun with that! Maybe other Haiku devs will agree with you that it’s important. I moved all my work to 64bit and I’m not really looking back.

During the transitional phase, we will declare llvm_clang$secondaryArchSuffix as a BUILD_PREREQUIRES for the recipe. If I am not mistaken, this is allowed, right? We can even build Clang for primary using the secondary Clang this way.

Then, for future builds, we just need to use primary arch Clang to build primary ports, without any references to a secondary.

I don’t think this is possible without some additional step, something like is done in the recipe for perl? @jmairboeck would know more about that.