Development of rustc_codegen_gcc

It’s been a while since I wrote my last progress report about rustc_codegen_gcc (if you don’t know what this project is about, please look at the description in the last project report). We’ve been having some trouble making big progress lately, so I wanted to take a break from the usual article format that only showed what progress has been made and instead write something more technical so you see what’s making it hard for us to focus on making rustc_codegen_gcc ready to use.

Sync with upstream Rust

In today’s article, I wanted to talk about what we need to do to sync with the Rust compiler. Like what happened at the beginning of the summer, we’re still having issues with making the sync with the Rust compiler.

What is a sync?

So, what’s a sync with the Rust compiler? Before I explain what it is, I’ll explain how the GCC codegen works: it is a shared library that is loaded by the rust compiler. It implements a bunch of traits from the rustc_codegen_ssa crate: the functions in them specify how the specific backend generates code for a specific operation, for instance how to add 2 integers. rustc_codegen_gcc pins a specific nightly version of the Rust compiler so that it continues to work even if the API changes on the rustc side. So when I say I "sync with the Rust compiler", I mean that I increment this nightly version to one that is closer to today and pull the changes from the Rust repo: rustc_codegen_gcc indeed lives in a separate repo and the Rust repo includes the project via a git subtree. I can also sync back the changes from the subtree into the Rust repo (it’s much easier to sync this way). We need to do this to stay up-to-date with the most recent changes of the Rust compiler and get the new features implemented.

What makes it hard to do a sync?

In this blog post, I’ll only talk about why the current sync is hard to do. I might write other blog posts in the future about other issues with the sync and the different aspects of the development of rustc_codegen_gcc if there’s an interest.

In the CI of the GCC codegen repo, we run many tests to make sure the codegen keeps working. However, most of the time when we do a sync, some of these tests will fail, so we need to investigate why and the reason of those failures can take a long time to understand and to fix. As mentioned in the last progress report, we even disabled some tests to be able to finish the sync and move on: none of those tests were re-enabled yet since we’re busy trying to do this sync.

What do we need to do to sync with the Rust compiler?

The first thing that we usually need to do is to update the patches we have. Most of them are related to testing: some will disable a few tests, others will allow us to run the tests of some sysroot crate more easily. It is fortunately easy to update these patches.

As mentioned previously, a sync will often break things. In this case, we had the following errors when compiling a program using the standard library:

/usr/bin/ld: /home/runner/work/rustc_codegen_gcc/rustc_codegen_gcc/build/build_sysroot/sysroot/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd.rlib(std.std.c6df57227ece985b-cgu.11.rcgu.o):(.data.rel._rust_extern_with_linkage_pidfd_spawnp+0x0):
undefined reference to `pidfd_spawnp'
/usr/bin/ld: /home/runner/work/rustc_codegen_gcc/rustc_codegen_gcc/build/build_sysroot/sysroot/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd.rlib(std.std.c6df57227ece985b-cgu.11.rcgu.o):(.data.rel._rust_extern_with_linkage_pidfd_getpid+0x0):
undefined reference to `pidfd_getpid'

This required a bit of investigation to figure out why this was happening. We found out that we needed to add the support for the weak variable attribute in order to fix this particular issue and to make some progress on the sync. This required adding support for it in libgccjit which is a bit of work.

The next thing that was a problem for this sync is that there were many AVX-512 intrinsics that were added. Why is this a problem for us? It’s because they do not map 1-to-1 between GCC and LLVM. As such, we need to maintain a mapping between them: not only their name is different, but sometimes, their number of arguments or their order is different. So we end up with code like this to rearrange the arguments. We also sometimes need to adjust the return value as can be seen below in this file.

The process to create this mapping is cumbersome:

  1. We compile stdarch (which takes close to 1 minute to complete with the GCC codegen on my PC).

  2. We look at the error telling us which intrinsic is missing.

  3. We add the mapping for this intrinsic.

  4. We go back to step 1.

Since in the current sync (which updates from Rust nightly-2024-07-02 to nightly-2024-08-11) there were more than 100 new intrinsics, this process can take time.

To help go a bit faster, I modified a bit the code so that it doesn’t panic on the first missing intrinsic, but it will rarely show more than 5 missing intrinsics at a time since it will eventually fail with a type mismatch error in GCC at some point when I do that.

To help us with this process, we created a script that will try to auto-generate this mapping: it generates this file. However, since it uses files from LLVM, it’s not perfect: it’s missing many intrinsics and, sometimes, the name of the GCC builtins is wrong. This also doesn’t help when the arguments need to be rearranged.

And that’s mostly where we’re at for the current sync: we’re close to being done with it (more than a month after we started). There are still errors when running the stdarch tests, though. One of them is actually a bug in GCC since it generates invalid assembly when using the Intel syntax: we now get this error:

/tmp/libgccjit-4MRR1f/fake.s: Assembler messages:
/tmp/libgccjit-4MRR1f/fake.s:171999: Error: operand size mismatch for `vfpclasssd'
/tmp/libgccjit-4MRR1f/fake.s:172085: Error: operand size mismatch for `vfpclassss'

This is not the first time I see this since the Intel syntax is much less tested in GCC than the AT&T syntax: in fact, I already fixed a similar issue in the past. Since I’m less familiar with the backend part of GCC, it’s going to take some time before I find how to fix this bug.

We also have another error that we need to fix:

libgccjit.so: crates/core_arch/src/x86/avx512ifma.rs:136:5: error: :
‘__builtin_ia32_vpmadd52huq256_mask’ needs isa option -mavx512ifma -mavx512vl

This seems to suggest that we do something wrong with enabling target features at the function level: I haven’t started investigating this issue yet, though, so I might be wrong.

Prioritization

All of that made me think perhaps it would help quite a bit to shift our priorities. For instance, focusing on the latest AVX-512 intrinsics could perhaps be delayed so that the sync can be made faster and more easily. Doing so would allow us to work on other, more important, features. I’d still like to keep running the stdarch tests in our CI, though, so that what we have continues to work: I’ll try to find a way to make sure the tests compile even without the missing intrinsics and to ignore the tests in this case. Doing so could also create relatively simple tasks for new potential contributors: they could add a few missing intrinsics every now and then, a few at a time.

As I continue to do this exercise of explaining the work we do on rustc_codegen_gcc, I might think about other prioritization issues we might have.

Conclusion

As you can see, we’re struggling to keep up with the changes in rustc. For the current sync, the issue mostly was trying to stay up-to-date to support the newest AVX-512 intrinsics. Doing so, we found a bug in the GCC backend which we’ll need to fix to complete this sync.

I also proposed a solution to help us go faster with these sync by prioritizing more important work.

When we’ll have more tests running in the CI of the Rust repo, it will help us with these sync because we’ll be able to fix one issue at a time without having to investigate much about what’s the cause of the issue. Some of these issues might even be fixed by the contributor making the PR if it’s easy.

I might write more blog posts like this in the future, so stay tuned!