Rust on Dreamcast
Preliminary support exists for developing for the Dreamcast using the Rust programming language. This is a bit of a challenge, however, as the official Rust compiler is based on the LLVM toolchain infrastructure, which does not support the Dreamcast CPU's SuperH architecture. Dreamcast programming is instead done with GCC, the GNU Compiler Collection. There exists two solutions to this problem:
- rustc_codegen_gcc: A libgccjit codegen backend for rustc (preferred method)
- gccrs: a Rust frontend for GCC
rustc_codegen_gcc
GCC includes a component called libgccjit which provides an interface for an embeddable just-in-time code generator, useful for creating interpreters. However, this component can also be used to generate code ahead of time as well. rustc_codegen_gcc is a project which interfaces the official Rust compiler frontend with the libgccjit API to generate machine code from Rust using the GCC backend. Using this, we can compile Rust programs for Dreamcast using familiar compiler tools such as rustc
and cargo
! The familiar borrow checker still works, and one can write #![no_std]
crates with full libcore
support. An experimental crate binding with KallistiOS provides liballoc
functionality such as a heap and familiar collections like Vec
, String
, etc. as well.
We will build rustc_codegen_gcc support for the Dreamcast in the instructions below. Before we begin, though:
- You must already have a KallistiOS development environment set up. This means you have created a cross-compiling toolchain for SH4 and you have built KallistiOS with it. See Getting Started with Dreamcast development for more information.
- For the purposes of this guide, we will assume you are using the standard paths for Dreamcast development tools; i.e. your environment is set up in
/opt/toolchains/dc
. - Your KallistiOS installation will need its floating point precision setting set to
m4-single
. This setting is available in theenviron.sh
, but changing the setting may require you to rebuild your main toolchain if you have not built it withm4-single
support. Once you modify the setting in yourenviron.sh
and re-source theenviron.sh
, you'll need to rebuild KallistiOS for the changes to take effect.
- For the purposes of this guide, we will assume you are using the standard paths for Dreamcast development tools; i.e. your environment is set up in
- You must already have a relatively up-to-date Rust installation, either using your operating system's package manager or rustup.
Building a cross-compiling libgccjit.so for rustc_codegen_gcc
Before we can use rustc_codegen_gcc, we must compile libgccjit.so
, the libgccjit library, for your system. This entails building a unique copy of the SH4 toolchain in its own directory under /opt/toolchains/dc/rust
, using a forked version of GCC with enhancements made to libgccjit.
Using git
, clone the rust-for-dreamcast
repository to /opt/toolchains/dc/rust
:
git clone https://github.com/darcagn/rust-for-dreamcast /opt/toolchains/dc/rust
Enter your KallistiOS installation's dc-chain
directory:
cd /opt/toolchains/dc/kos/utils/dc-chain
Clear out any existing build files:
make clean-keep-archives
Copy the necessary toolchain patches to your dc-chain
setup:
cp /opt/toolchains/dc/rust/toolchain/*.diff patches/
Copy the rustc_codegen_gcc configuration file into place:
cp /opt/toolchains/dc/rust/toolchain/config.mk.rustc_codegen_gcc.sample config.mk
Make any desired changes to the configuration (e.g., change makejobs=-j2
to the number of CPU threads you'd like to use during compilation), and then compile the SH4 toolchain:
make build-sh4
When this command is completed successfully, a libgccjit.so
will be installed to /opt/toolchains/dc/rust/sh-elf/lib/libgccjit.so
.
Building rustc_codegen_gcc
The rust-for-dreamcast
repository contains scripts and wrappers to assist you in building rustc_codegen_gcc and using it in conjunction with cargo
and rustc
. We'll need to add the path to those scripts to our PATH
environment variable:
export PATH="/opt/toolchains/dc/rust/bin:$PATH"
You may also want to add the above lines to your shell's startup file or else you'll need to run them every time you open a new shell.
Clone the rustc_codegen_gcc to your rust directory:
git clone https://github.com/rust-lang/rustc_codegen_gcc.git /opt/toolchains/dc/rust/rustc_codegen_gcc
Set the gcc_path
file to the location of our libgccjit.so
library file:
echo /opt/toolchains/dc/rust/sh-elf/lib > /opt/toolchains/dc/rust/rustc_codegen_gcc/gcc_path
Various patches need to be applied to rustc_codegen_gcc for it to compile properly for our target platform. Let's apply them:
rcg-dc patch
Now let's build rustc_codegen_gcc!
rcg-dc prepare rcg-dc build
Compiling individual modules into object files with rustc
A wrapper script named rustc-dc
is included for generating .o
object files with rustc
for inclusion in a KallistiOS Makefile
-based project. If we assume the module file is named example.rs
, you'll need to add example.o
as an object file in your Makefile
's OBJS =
declaration. Additionally, you'll need to add the following lines so that make
knows how to compile Rust modules into .o
object files:
%.o: %.rs
rustc-dc $< -o $@
Alternatively, you can add those lines to your KallistiOS Makefile.rules
file to avoid having to place it in every project's Makefile
.
An example "Hello, world!" program built in this style which also demonstrates basic C interoperation is included with the Rust-for-Dreamcast repository, located at examples/rustc-hello
.
Creating a new project using Cargo
A wrapper script called cargo-dc
is included to simplify invoking cargo
using rustc_codegen_gcc. When using cargo
in this setup, we will compile our program and all crate code into a static library .a
file and link it with an empty KallistiOS Makefile
-style project.
Create a new crate using cargo-dc
:
cargo-dc new example --lib
Change the crate to a static library in Cargo.toml
by changing the crate-type
as follows:
crate-type = ["staticlib"]
- add kos-rs crate from /opt/toolchains/dc/rust/kos-rs path
gccrs
gccrs implements a new Rust compiler frontend for GCC. This essentially means creating a separate new Rust compiler from the ground up using the GCC toolchain infrastructure. This project is in early stages and is targeting the Rust 1.49 revision from December 2020. As of this writing (February 2024), it is not yet able to compile Rust's libcore
, so many basic language features are unimplemented or not functional. Additionally, Rust standard tooling like cargo
is not available. Borrow checking is not implemented, but the project plans to later use the next-generation Rust borrow checker Polonius from the official Rust project.
It is possible to use this compiler by building the GCC 14.0.1-dev toolchain or the gccrs latest toolchain. GCC 14.0.1-dev will get you the latest code upstreamed by the gccrs team into the main development branch of GCC, while the gccrs git repo will get you the absolute latest bleeding edge updates to gccrs. The GCC 14.0.1-dev configuration file is available within the official KallistiOS repo's dc-chain
script, while the latest gccrs configuration is available within the Rust for Dreamcast repository. Brief instructions follow for setting up the latest gccrs toolchain. See Getting Started with Dreamcast development for more detailed information on how to set up and run dc-chain
.
Building a gccrs-enabled toolchain
Follow the Getting Started with Dreamcast development guide for creating a Dreamcast toolchain until you arrive at the instructions for setting up the dc-chain
configuration file. At this point, you should have a shell open to /opt/toolchains/dc/kos/utils/dc-chain
.
Clone the Rust for Dreamcast repository:
git clone https://github.com/darcagn/rust-for-dreamcast.git rust
Copy the GCC patch in place:
cp rust/toolchain/gcc-rs-kos.diff patches/
Copy the dc-chain
configuration file into place:
cp rust/toolchain/config.mk.gccrs.sample config.mk
Make any desired changes to the configuration (e.g., change makejobs=-j2
to the number of CPU threads you'd like to use during compilation). Note that to avoid conflicting with an existing stable toolchain at the default path (i.e. /opt/toolchains/dc/sh-elf
), we will be installing to /opt/toolchains/dc/gccrs/sh-elf
instead. To begin compilation and installation, run:
make build-sh4
After building everything, you can clean up the extraneous files in your dc-chain
directory by entering:
make clean
Setting up Makefiles to compile Rust modules
As mentioned before, cargo
is not available to use with gccrs, so for our example, we will place our .rs
modules within a typical KallistiOS Makefile
project. If we assume the module file is named example.rs
, you'll need to add example.rox
as an object file in your Makefile
's OBJS =
declaration. Additionally, you'll need to add the following lines so that make
knows how to compile Rust modules into rox
object files:
%.rox: %.rs
kos-cc -frust-incomplete-and-experimental-compiler-do-not-use $(CFLAGS) -c $< -o $@
Alternatively, you can add those lines to your KallistiOS Makefile.rules
file to avoid having to place it in every project's Makefile
.
In your example.rs
file, your main
function will need to be declared like so:.
#[no_mangle]
pub extern fn main() -> i32 {
[...]
}
Make sure before you compile your code that you set export KOS_CC_BASE="/opt/toolchains/dc/gccrs/sh-elf"
in your KallistiOS environ.sh
file or make
will not find your gccrs compiler executable.