
Change __rust_no_alloc_shim_is_unstable to be a function This fixes a long sequence of issues: 1. A customer reported that building for Arm64EC was broken: #138541 2. This was caused by a bug in my original implementation of Arm64EC support, namely that only functions on Arm64EC need to be decorated with `#` but Rust was decorating statics as well. 3. Once I corrected Rust to only decorate functions, I started linking failures where the linker couldn't find statics exported by dylib dependencies. This was caused by the compiler not marking exported statics in the generated DEF file with `DATA`, thus they were being exported as functions not data. 4. Once I corrected the way that the DEF files were being emitted, the linker started failing saying that it couldn't find `__rust_no_alloc_shim_is_unstable`. This is because the MSVC linker requires the declarations of statics imported from other dylibs to be marked with `dllimport` (whereas it will happily link to functions imported from other dylibs whether they are marked `dllimport` or not). 5. I then made a change to ensure that `__rust_no_alloc_shim_is_unstable` was marked as `dllimport`, but the MSVC linker started emitting warnings that `__rust_no_alloc_shim_is_unstable` was marked as `dllimport` but was declared in an obj file. This is a harmless warning which is a performance hint: anything that's marked `dllimport` must be indirected via an `__imp` symbol so I added a linker arg in the target to suppress the warning. 6. A customer then reported a similar warning when using `lld-link` (<https://github.com/rust-lang/rust/pull/140176#issuecomment-2872448443>). I don't think it was an implementation difference between the two linkers but rather that, depending on the obj that the declaration versus uses of `__rust_no_alloc_shim_is_unstable` landed in we would get different warnings, so I suppressed that warning as well: #140954. 7. Another customer reported that they weren't using the Rust compiler to invoke the linker, thus these warnings were breaking their build: <https://github.com/rust-lang/rust/pull/140176#issuecomment-2881867433>. At that point, my original change was reverted (#141024) leaving Arm64EC broken yet again. Taking a step back, a lot of these linker issues arise from the fact that `__rust_no_alloc_shim_is_unstable` is marked as `extern "Rust"` in the standard library and, therefore, assumed to be a foreign item from a different crate BUT the Rust compiler may choose to generate it either in the current crate, some other crate that will be statically linked in OR some other crate that will by dynamically imported. Worse yet, it is impossible while building a given crate to know if `__rust_no_alloc_shim_is_unstable` will statically linked or dynamically imported: it might be that one of its dependent crates is the one with an allocator kind set and thus that crate (which is compiled later) will decide depending if it has any dylib dependencies or not to import `__rust_no_alloc_shim_is_unstable` or generate it. Thus, there is no way to know if the declaration of `__rust_no_alloc_shim_is_unstable` should be marked with `dllimport` or not. There is a simple fix for all this: there is no reason `__rust_no_alloc_shim_is_unstable` must be a static. It needs to be some symbol that must be linked in; thus, it could easily be a function instead. As a function, there is no need to mark it as `dllimport` when dynamically imported which avoids the entire mess above. There may be a perf hit for changing the `volatile load` to be a `tail call`, so I'm happy to change that part back (although I question what the codegen of a `volatile load` would look like, and if the backend is going to try to use load-acquire semantics). Build with this change applied BEFORE #140176 was reverted to demonstrate that there are no linking issues with either MSVC or MinGW: <https://github.com/rust-lang/rust/actions/runs/15078657205> Incidentally, I fixed `tests/run-make/no-alloc-shim` to work with MSVC as I needed it to be able to test locally (FYI for #128602) r? `@bjorn3` cc `@jieyouxu`
WIP libgccjit codegen backend for rust
This is a GCC codegen for rustc, which means it can be loaded by the existing rustc frontend, but benefits from GCC: more architectures are supported and GCC's optimizations are used.
Despite its name, libgccjit can be used for ahead-of-time compilation, as is used here.
Motivation
The primary goal of this project is to be able to compile Rust code on platforms unsupported by LLVM. A secondary goal is to check if using the gcc backend will provide any run-time speed improvement for the programs compiled using rustc.
Getting Started
Note: This requires a patched libgccjit in order to work.
You need to use my fork of gcc which already includes these patches.
The default configuration (see below in the Quick start section) will download a libgccjit
built in the CI that already contains these patches, so you don't need to build this fork yourself if you use the default configuration.
Dependencies
- rustup: follow instructions on the official website
- consider to install DejaGnu which is necessary for running the libgccjit test suite. website
- additional packages:
flex
,libmpfr-dev
,libgmp-dev
,libmpc3
,libmpc-dev
Quick start
-
Clone and configure the repository:
git clone https://github.com/rust-lang/rustc_codegen_gcc cd rustc_codegen_gcc cp config.example.toml config.toml
-
Build and test:
./y.sh prepare # downloads and patches sysroot ./y.sh build --sysroot --release # Verify setup with a simple test ./y.sh cargo build --manifest-path tests/hello-world/Cargo.toml # Run full test suite (expect ~100 failing UI tests) ./y.sh test --release
If don't need to test GCC patches you wrote in our GCC fork, then the default configuration should
be all you need. You can update the rustc_codegen_gcc
without worrying about GCC.
Building with your own GCC version
If you wrote a patch for GCC and want to test it without this backend, you will need to do a few more things.
To build it (most of these instructions come from here, so don't hesitate to take a look there if you encounter an issue):
$ git clone https://github.com/rust-lang/gcc
$ sudo apt install flex libmpfr-dev libgmp-dev libmpc3 libmpc-dev
$ mkdir gcc-build gcc-install
$ cd gcc-build
$ ../gcc/configure \
--enable-host-shared \
--enable-languages=jit \
--enable-checking=release \ # it enables extra checks which allow to find bugs
--disable-bootstrap \
--disable-multilib \
--prefix=$(pwd)/../gcc-install
$ make -j4 # You can replace `4` with another number depending on how many cores you have.
If you want to run libgccjit tests, you will need to also enable the C++ language in the configure
:
--enable-languages=jit,c++
Then to run libgccjit tests:
$ cd gcc # from the `gcc-build` folder
$ make check-jit
# To run one specific test:
$ make check-jit RUNTESTFLAGS="-v -v -v jit.exp=jit.dg/test-asm.cc"
Put the path to your custom build of libgccjit in the file config.toml
.
You now need to set the gcc-path
value in config.toml
with the result of this command:
$ dirname $(readlink -f `find . -name libgccjit.so`)
and to comment the download-gccjit
setting:
gcc-path = "[MY PATH]"
# download-gccjit = true
Then you can run commands like this:
$ ./y.sh prepare # download and patch sysroot src and install hyperfine for benchmarking
$ ./y.sh build --sysroot --release
To run the tests:
$ ./y.sh test --release
Usage
You have to run these commands, in the corresponding order:
$ ./y.sh prepare
$ ./y.sh build --sysroot
To check if all is working correctly, run:
$ ./y.sh cargo build --manifest-path tests/hello-world/Cargo.toml
Cargo
$ CHANNEL="release" $CG_GCCJIT_DIR/y.sh cargo run
If you compiled cg_gccjit in debug mode (aka you didn't pass --release
to ./y.sh test
) you should use CHANNEL="debug"
instead or omit CHANNEL="release"
completely.
LTO
To use LTO, you need to set the variable EMBED_LTO_BITCODE=1
in addition to setting lto = "fat"
in the Cargo.toml
.
Failing to set EMBED_LTO_BITCODE
will give you the following error:
error: failed to copy bitcode to object file: No such file or directory (os error 2)
Rustc
If you want to run rustc
directly, you can do so with:
$ ./y.sh rustc my_crate.rs
You can do the same manually (although we don't recommend it):
$ LIBRARY_PATH="[gcc-path value]" LD_LIBRARY_PATH="[gcc-path value]" rustc +$(cat $CG_GCCJIT_DIR/rust-toolchain | grep 'channel' | cut -d '=' -f 2 | sed 's/"//g' | sed 's/ //g') -Cpanic=abort -Zcodegen-backend=$CG_GCCJIT_DIR/target/release/librustc_codegen_gcc.so --sysroot $CG_GCCJIT_DIR/build_sysroot/sysroot my_crate.rs
Environment variables
- CG_GCCJIT_DUMP_ALL_MODULES: Enables dumping of all compilation modules. When set to "1", a dump is created for each module during compilation and stored in
/tmp/reproducers/
. - CG_GCCJIT_DUMP_MODULE: Enables dumping of a specific module. When set with the module name, e.g.,
CG_GCCJIT_DUMP_MODULE=module_name
, a dump of that specific module is created in/tmp/reproducers/
. - CG_RUSTFLAGS: Send additional flags to rustc. Can be used to build the sysroot without unwinding by setting
CG_RUSTFLAGS=-Cpanic=abort
. - CG_GCCJIT_DUMP_TO_FILE: Dump a C-like representation to /tmp/gccjit_dumps and enable debug info in order to debug this C-like representation.
- CG_GCCJIT_DUMP_RTL: Dumps RTL (Register Transfer Language) for virtual registers.
- CG_GCCJIT_DUMP_RTL_ALL: Dumps all RTL passes.
- CG_GCCJIT_DUMP_TREE_ALL: Dumps all tree (GIMPLE) passes.
- CG_GCCJIT_DUMP_IPA_ALL: Dumps all Interprocedural Analysis (IPA) passes.
- CG_GCCJIT_DUMP_CODE: Dumps the final generated code.
- CG_GCCJIT_DUMP_GIMPLE: Dumps the initial GIMPLE representation.
- CG_GCCJIT_DUMP_EVERYTHING: Enables dumping of all intermediate representations and passes.
- CG_GCCJIT_KEEP_INTERMEDIATES: Keeps intermediate files generated during the compilation process.
- CG_GCCJIT_VERBOSE: Enables verbose output from the GCC driver.
Extra documentation
More specific documentation is available in the doc
folder:
- Common errors
- Debugging GCC LTO
- Debugging libgccjit
- Git subtree sync
- List of useful commands
- Send a patch to GCC
Licensing
While this crate is licensed under a dual Apache/MIT license, it links to libgccjit
which is under the GPLv3+ and thus, the resulting toolchain (rustc + GCC codegen) will need to be released under the GPL license.
However, programs compiled with rustc_codegen_gcc
do not need to be released under a GPL license.