Tue 02 June 2020

Improving RISC-V Linux support in Rust

by Tom Eccles , 2020 , Tags RISC-V Linux Rust Open Source
Article image

RISC-V is a new Instruction Set Architecture developed in the open and available for use without paying a license fee. Being an open ISA means there are no barriers to achieving open hardware implementations - which opens the door to performant (mostly) open hardware processors!

RISC-V is considerably newer than most ISAs and so can draw on decades more computer architecture research and practical experience. Many of the improvements are well explained in "The RISC-V Reader" by David Patterson and Andrew Waterman.

Rust is a modern systems programming language which uses an innovative type system to track ownership of all data in a program. This allows automatic memory management without a language runtime, multithreaded programs which are provably free of data races, and the guarantee that all values are correctly typed. Furthermore, Rust brings the conveniences of a modern programming language to systems programming: with built in testing and dependency management, and an excellent standard library.

Codethink sponsored me to improve the support for Rust on RISC-V Linux. I was excited to do this because I consider both Rust and RISC-V to be the future of computer systems engineering.

Finding my feet

When I started work there was already an issue tracking riscv64gc-unknown-linux-gnu (64-bit RISC-V base instruction set plus compressed instructions on GNU+Linux) Rust support; Rust's compiler already had support for riscv64gc-unknown-linux-gnu; and there were even Debian packages.

This was surprising because the Rust project didn't list RISC-V Linux support on the supported platforms page. When asking about this, I was pointed towards the RFC defining criteria for official Rust support at each of three tiers. To summarise briefly:

  • Tier 1 targets will always build and pass tests (e.g. x86_64 GNU+Linux).
  • Tier 2 targets will always build, but tests may not pass (e.g. aarch64 Android).
  • Tier 3 targets provide no guarantees of support (e.g. MIPS ulibc+Linux).

There were very few requirements for official Tier 3 support so I submitted a PR advertising Tier 3 Rust support for riscv64gc-unknown-linux-gnu. This is merged so Rust now advertises support for RISC-V Linux.

In the long run I would like to see riscv64gc-unknown-linux-gnu Tier 2 support so that the Rust Project distribute official binaries: making Rust easy to install on RISC-V Linux using rustup (the standard Rust installer). But more work is needed first.

RISC-V Rust tooling support

For rustup to be useful on RISC-V Linux hosts it needs official Rust binaries to install; minor code changes to tell rustup that it knows how to install riscv64gc-unknown-linux programs; and a CI pipeline to build rustup for RISC-V Linux.

When I tried out the code and CI rustup changes I found that effective-limits (one of rustup's dependencies) didn't build correctly for RISC-V Linux. This was in turn because of a bug in libc (Rust's bindings for the standard C library). I wrote a patch to fix libc but misizanoen1 posted a PR for this first. I liaised with the libc maintainers to get this PR merged and to bump libc's version so that effective-limits can depend upon the fixed version of libc.

At the time of writing I have completed all changes to rustup and the PR is ready for review, following the merge of my effective-limits PR to add the dependency on the fixed version of libc.

Besides rustup support for running on RISC-V Linux hosts, rustup also supports installing cross-compilation targets to build RISC-V binaries from other hosts (e.g. from my x86_64 GNU+Linux Laptop). This worked out of the box because all rustc targets are built for each host for which the Rust project distribute binaries. However, instead of interfacing directly with rustup to install foreign targets, the usual way for Rust developers to cross compile is to use the cross wrapper program.

cross was also missing RISC-V support. My PR to cross is here. There was a spurious CI failure for an unrelated architecture, which I also fixed.

Testing Rust on RISC-V

With the low hanging fruit out of the way, one of the biggest blockers for Rust Tier 2 RISC-V Linux support is documentation explaining how to run the compiler and standard library tests. In the case of RISC-V Linux, tests must run in an emulator because real hardware is not easily available.

Rust already supports emulated testing for arm-unknown-linux-gnueabihf and aarch64-unknwon-linux-gnu. These work using a remote-test-client program running natively on the build machine to send cross-compiled test programs and libraries to the remote-test-server which runs the tests from within an emulated system and sends the results, stdout and stderr back to the client for reporting.

I followed this pattern for riscv64gc-unknown-linux-gnu, building a rootfs image for QEMU roughly following instructions from Debian but instead using Ubuntu 20.04 because Debian "Unstable" isn't intended for production use.

The whole build is wrapped in a Docker container, run using a wrapper script:

$ ./src/ci/docker/run.sh <CONTAINER>

Where <CONTAINER> is the architecture to build and test e.g. aarch64-gnu or riscv64gc-linux.

When running the tests in the RISC-V emulator I found that many tests hang indefinitely. In general, these tests use multi-threading, multi-processing or depend upon stack unwinding on a panic. These tests need further work so I haven't upstreamed the RISC-V Linux testing support yet.

The state of Rust on RISC-V

The hanging tests indicate there's more work required to provide full language support on RISC-V. However, the compiler works well enough to build itself and most other crates, and tooling support is ready.

Reflections

I had little experience working with upstream open source communities before this project. The Rust community have great documentation and have been both friendly and helpful - making this a perfect place to start.

While I haven't implemented anything technically complex or unusual, I hope my contributions can be useful to the community by getting much of the time consuming boilerplate CI and tooling work out of the way so that other contributors can work on the more exciting problems. I was surprised how much behind the scenes work is involved in providing CI, testing and tooling to give Rust its distinctive polish; and I am pleased to have the opportunity to contribute in a small way to that.

Want to learn more?

Sharing Technical Knowledge at Codethink

Codethink Engineers Develop Custom Debug Solution for Customer Project

Get in touch