Rust on Dreamcast: Difference between revisions

From dreamcast.wiki
Jump to navigation Jump to search
 
(134 intermediate revisions by the same user not shown)
Line 1: Line 1:
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's CPU SuperH architecture. Dreamcast programming is instead done with GCC, the GNU Compiler Collection. There exists two solutions to this problem:
[[File:Rust-dc-logo.png|thumb|Ferris holding his Dreamcast controller]]


* rustc_codegen_gcc: A libgccjit codegen backend for rustc (preferred method)
'''Rust''' is a systems programming language rising in popularity which emphasizes memory safety and performance. Due to its operating at a low level, it is an ideal candidate for running on the Dreamcast. Doing so presents a bit of a challenge, however, as the official Rust compiler is based on the [https://llvm.org/ LLVM] toolchain infrastructure, which does not support the Dreamcast CPU's SuperH architecture. Dreamcast programming is instead typically done with [https://gcc.gnu.org/ GCC], the GNU Compiler Collection. There are currently two viable solutions to this challenge:
* gccrs: a Rust frontend for GCC


=rustc_codegen_gcc=
* '''rustc_codegen_gcc''': A libgccjit-based codegen backend for rustc (preferred method)
GCC includes a component called '''libgccjit''' which provides an API for an embeddable just-in-time code generator using GCC, useful for creating programs like 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 with the libgccjit API to generate machine code from Rust using the GCC backend. With this, we can compile Rust programs for Dreamcast using familiar compiler tools such as <code>rustc</code> and <code>cargo</code>! The familiar borrow checker still works, and one can write <code>#![no_std]</code> crates with full <code>libcore</code> support. An experimental crate binding with [[KallistiOS]] provides <code>liballoc</code> functionality such as a heap and familiar collections like Vec, String, etc. as well.
* '''gccrs''': a Rust frontend for GCC
 
While neither solution is complete at this time, '''rustc_codegen_gcc''' is much further along and is quite usable with some patience with its current limitations and rapid change. On the other hand, '''gccrs''' can compile for Dreamcast, but is in a very early stage, with much of the language unimplemented and no '''libcore''' support. Below we will focus on using rustc_codegen_gcc. For more information on using gccrs, see the [[gccrs]] page.
 
=Building rustc_codegen_gcc to develop on Dreamcast=
With [https://github.com/rust-lang/rustc_codegen_gcc rustc_codegen_gcc], we can interface the standard '''rustc''' compiler frontend with '''libgccjit''', a GCC code-generation API. With the help of the [https://github.com/darcagn/rust-for-dreamcast '''Rust-for-Dreamcast''' repo] and the [https://github.com/darcagn/kos-rs '''kos-rs''' crate] containing some early basic [[KallistiOS]] bindings, we can set up rustc_codegen_gcc to compile Rust programs with [https://doc.rust-lang.org/core/ '''core'''] and [https://doc.rust-lang.org/alloc/ '''alloc'''] support (but not the entirety of [https://doc.rust-lang.org/std/ '''std''']). Rust-for-Dreamcast includes wrapper scripts to invoke the rustc and '''cargo''' tools in a familiar way. The familiar borrow checker still works, and one can import and use <code>no_std</code> crates. Despite this support, rustc_codegen_gcc is still in active development, so if using such a setup, expect that things may change rapidly over time. We will need to use some provided patches and scripts to make this solution work. See the rustc_codegen_gcc [https://blog.antoyo.xyz/ progress reports] for more information on the upstream project's progress.
 
'''What Works'''
* '''libcore''' -- the core components of the language for running on bare metal (basics like integers, floats, enums, bools, chars, tuples, arrays, slices, closures, iterators, etc.)
* '''liballoc''' -- the core components of the language that require a heap, including collections (Vec, String, Box, etc.)
* linking to KallistiOS -- KallistiOS and kos-ports can be used if one manually manages interoperating with C via '''unsafe'''
* including <code>no_std</code> crates with the <code>cargo</code> build system
'''In Progress Now'''
* '''libc''' support -- Adding KallistiOS support to Rust's libc crate
* '''libstd''' support -- built-in language support for I/O, networking, threads, time and date, HashMap/HashSet, unwinding on panic, etc.
'''Future Goals'''
* KallistiOS bindings -- properly idiomatic Rust support for KallistiOS
* Upstream libc/libstd support, inclusion as a tier 3 target officially
* Expansion of <code>cargo-dc</code> to support more dcdev-specific functionallity like generating Dreamcast disc images using metadata specified in <code>Cargo.toml</code>
 
==Prerequisites==


We will build rustc_codegen_gcc support for the Dreamcast in the instructions below. Before we begin, though:
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.
* You must already have a KallistiOS development environment set up. This means you have installed the typical dependencies, you have created a cross-compiling toolchain for SH4, you have set up your KallistiOS <code>environ.sh</code> file, and you have built KallistiOS with it. Ideally, you will already have at least some familiarity with KallistiOS dev already. See [[Getting Started with Dreamcast development]] for more information, as well as the [https://kos-docs.dreamcast.wiki/ KallistiOS Doxygen].
** For the purposes of this guide, we will assume you are using the standard paths for Dreamcast tools; i.e. your environment is set up in  <code>/opt/toolchains/dc</code>.
** 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  <code>/opt/toolchains/dc</code>. Some included scripts and examples may assume this.
* You must already have a relatively up-to-date Rust installation, either using your operating system's package manager or [https://rustup.rs/ rustup].
** Your KallistiOS installation will need its <code>KOS_SH4_PRECISION</code> setting set to <code>-m4-single</code>. At this time, rustc_codegen_gcc support will not compile with KallistiOS's default <code>-m4-single-only</code> setting. This setting can be changed in KallistiOS's <code>environ.sh</code>, but changing the setting may require you to rebuild your toolchain if you have not built it with <code>m4-single</code> support (which is off by default, but can be enabled in the <code>config.mk</code> file). Once you modify the setting in your <code>environ.sh</code> and re-source the <code>environ.sh</code>, you'll need to rebuild KallistiOS with a <code>make clean</code> and <code>make</code> for the changes to take effect. '''kos-ports''' being used will also need rebuilding with <code>-m4-single</code>. Keep in mind, however, that because KallistiOS doesn't officially support <code>-m4-single</code> yet, some things may be broken, especially libraries in kos-ports that haven't been heavily tested with this setting.
* You must already have a relatively up-to-date Rust installation using [https://rustup.rs/ rustup]. Ideally, you will already have some familiarity with Rust's tools.
* Install the <code>jq</code> and <code>xxd</code> packages for your operating system. <code>xxd</code> might be part of <code>vim</code> depending on the organization of your operating system's package manager.
 
''If you run into any errors or other challenges while following this tutorial, or simply need clarification on any of the steps, feel free to ask for assistance on the [https://dcemulation.org/phpBB/viewforum.php?f=29 message board] and we would be happy to aid you and update the guide for the benefit of future readers and others in the community.''


==Building a cross-compiling libgccjit.so for rustc_codegen_gcc==
==Building a cross-compiling libgccjit.so for rustc_codegen_gcc==
First, we must compile <code>libgccjit.so</code>, the cross-compiling shared library, for your system. This entails building another copy of the SH4 toolchain once more in its own directory under <code>/opt/toolchains/dc/rust</code>, using a forked version of GCC with enhancements to '''libgccjit'''.
Before we can use rustc_codegen_gcc, we must compile <code>libgccjit.so</code>, the libgccjit library, for your system. This entails building a unique copy of the SH4 toolchain in its own directory under <code>/opt/toolchains/dc/rust</code>, using a forked version of GCC with enhancements made to libgccjit. The forked version is based on the latest GCC 14 development branch.
* '''NOTE''': This forked version of GCC 14 with libgccjit changes is actively developed alongside rustc_codegen_gcc itself, so if you update your rustc_codegen_gcc installation later, you may also need to rebuild libgccjit to pull down new changes rustc_codegen_gcc depends upon as well.


Using <code>git</code>, clone the <code>rust-for-dreamcast</code> repository to <code>/opt/toolchains/dc/rust</code>:
We will first clone the Rust-for-Dreamcast repository, which contains various supporting files needed to create Rust support for Dreamcast. Using <code>git</code>, clone the repository to <code>/opt/toolchains/dc/rust</code>:
  git clone https://github.com/darcagn/rust-for-dreamcast /opt/toolchains/dc/rust
  git clone https://github.com/darcagn/rust-for-dreamcast /opt/toolchains/dc/rust
Enter your KallistiOS installation's <code>dc-chain</code> directory:
Enter your KallistiOS installation's <code>dc-chain</code> directory:
Line 21: Line 45:
Clear out any existing build files:
Clear out any existing build files:
  make clean-keep-archives
  make clean-keep-archives
Copy the necessary toolchain patches to your <code>dc-chain</code> setup:
Make any desired changes to thie <code>Makefile.cfg</code> configuration file (e.g., change <code>makejobs=2</code> to the number of CPU threads you'd like to use during compilation), and then compile the SH4 toolchain:
cp /opt/toolchains/dc/rust/toolchain/*.diff patches/
  make toolchain_profile=rustc sh_toolchain_path=/opt/toolchains/dc/rust/sh-elf enable_libgccjit=1
Copy the '''rustc_codegen_gcc''' configuration file into place:
When this command is completed successfully, a new SH4 cross-compiler toolchain will exist at <code>/opt/toolchains/dc/rust/sh-elf</code> and your  <code>libgccjit.so</code> will be installed to <code>/opt/toolchains/dc/rust/sh-elf/lib/libgccjit.so</code>.
cp /opt/toolchains/dc/rust/toolchain/config.mk.rustc_codegen_gcc.sample config.mk
Make any desired changes to the configuration (e.g., change <code>makejobs=-j2</code> 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 <code>libgccjit.so</code> will be installed to <code>/opt/toolchains/dc/rust/sh-elf/lib/libgccjit.so</code>.


==Building rustc_codegen_gcc==
==Building rustc_codegen_gcc==
The <code>rust-for-dreamcast</code> repository contains scripts and wrappers to assist you in building rustc_codegen_gcc and using it in conjunction with <code>cargo</code> and <code>rustc</code>. Let's add them to our environment's PATH now:
Now that we have libgccjit built, we can use rustc_codegen_gcc to interface with it to generate SuperH machine code from Rust. Clone the rustc_codegen_gcc repository to your rust directory:
git clone https://github.com/rust-lang/rustc_codegen_gcc.git /opt/toolchains/dc/rust/rustc_codegen_gcc
rustc_codegen_gcc needs a <code>config.toml</code> file that specifies the location of <code>libgccjit.so</code>. Let's write the the <code>gcc-path</code> to the location of our <code>libgccjit.so</code> library file in this file:
echo 'gcc-path = "/opt/toolchains/dc/rust/sh-elf/lib"' > /opt/toolchains/dc/rust/rustc_codegen_gcc/config.toml
The Rust-for-Dreamcast repository contains scripts and wrappers to assist you in building rustc_codegen_gcc and using it in conjunction with <code>cargo</code> and <code>rustc</code>. You'll need to add the path to those scripts to your <code>PATH</code> environment variable:
  export PATH="/opt/toolchains/dc/rust/bin:$PATH"
  export PATH="/opt/toolchains/dc/rust/bin:$PATH"
You may also want to add the above line to your shell's startup file or else you'll need to run it every time you open a new shell.
You may also want to add the above line to your shell's startup file or else you'll need to re-run it every time you start a new shell.
 
Now we can use the included Rust-for-Dreamcast scripts to set up rustc_codegen_gcc. 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 we can prepare and build rustc_codegen_gcc!
rcg-dc prepare
rcg-dc build
 
=Using Rust for Dreamcast=
If all went well, rustc_codegen_gcc will have built successfully.
 
You can now use the scripts included in the Rust for Dreamcast repo:
* the <code>rcg-dc</code> script can be used to rebuild the rustc_codegen_gcc code after updating or editing it
* the <code>rustc-dc</code> script can be used to compile Rust modules
* the <code>cargo-dc</code> script can be used to build Rust crates
 
Examples are included with the Rust for Dreamcast repo to help you get started:
* [https://github.com/darcagn/rust-for-dreamcast/tree/master/examples/cargo-hello <code>cargo-hello</code>] demonstrates how to create a simple "Hello, world!" application with KallistiOS using <code>cargo</code>
* [https://github.com/darcagn/rust-for-dreamcast/tree/master/examples/cargo-cube <code>cargo-cube</code>] demonstrates a Rust project using KallistiOS with GLdc
* [https://github.com/darcagn/rust-for-dreamcast/tree/master/examples/cargo-addlib <code>cargo-addlib</code>] demonstrates how to create a Rust library that can be included with a KallistiOS project
* [https://github.com/darcagn/rust-for-dreamcast/tree/master/examples/rustc-hello <code>rustc-hello</code>] demonstrates how to compile and include a Rust module into a standard KallistiOS <code>Makefile</code>-based project
 
==Creating a new Rust project with Cargo==
First, we'll demonstrate creating a new "Hello, world!" project with <code>cargo-dc</code>. This will follow the [https://github.com/darcagn/rust-for-dreamcast/tree/master/examples/cargo-hello <code>cargo-hello</code>] example included in the Rust-for-Dreamcast repo.
 
The Cargo-based examples rely on the [https://github.com/darcagn/kos-rs '''kos-rs'''] crate being present on your computer locally. This is in a separate repo, so let's pull it down now:
git clone https://github.com/darcagn/kos-rs /opt/toolchains/dc/rust/kos-rs
 
In a directory of your choosing, let's invoke <code>cargo-dc</code> to create a new project and then enter the directory:
cargo-dc new hello
cd hello
 
Let's add our kos-rs crate to gain access to current KallistiOS bindings. Open <code>Cargo.toml</code> in your text editor and add:
<syntaxhighlight lang="toml">
[dependencies]
kos = { package = "kos-rs",  path = "/opt/toolchains/dc/rust/kos-rs" }
</syntaxhighlight>
 
Now we can open up <code>src/main.rs</code> and write our "Hello, world!" example code:
<syntaxhighlight lang="rust" line>
#![no_std]
#![no_main]
extern crate alloc;
use kos::println;
 
#[no_mangle]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
    println!("Hello, world!");
    return 0;
}
</syntaxhighlight>
 
* <syntaxhighlight lang="rust" inline>#![no_std]</syntaxhighlight> and <syntaxhighlight lang="rust" inline>#![no_main]</syntaxhighlight> tell Rust that our project does not use the standard library and we will not have Rust use a <code>main</code> function as an entry point -- this will be handled by KallistiOS.
* <syntaxhighlight lang="rust" inline>extern crate alloc;</syntaxhighlight> tells Rust to use the alloc crate to gain access to heap-allocated types (in our case, <code>String</code>).
* <syntaxhighlight lang="rust" inline>use kos::println!;</syntaxhighlight> tells Rust to use the <code>println!</code> macro defined in the kos-rs crate. With this, we can print output to our <code>dc-tool</code> console.
* <syntaxhighlight lang="rust" inline>#[no_mangle]</syntaxhighlight> tells Rust to disable name mangling so that the <code>main</code> function can be used by KallistiOS.
* Finally, we have a <syntaxhighlight lang="rust" inline>fn main</syntaxhighlight> with the function signature of a typical C <code>main</code> function, containing a basic "Hello, world!" exclamation.
 
Now we can use <code>cargo-dc build</code> to build our project. If all goes well, there will be a <code>target/sh-elf/debug/hello.elf</code> file that can be sent to the Dreamcast with <code>dc-tool</code>.
If you have <code>KOS_LOADER</code> set in your KallistiOS environment, you can invoke it directly with <code>cargo-dc run</code>.
 
==Creating a Rust project using kos-ports libraries==
[[File:Rust-Cube rustc codegen-gcc demo.gif|thumb|cube example in action]]
The [https://github.com/darcagn/rust-for-dreamcast/tree/master/examples/cargo-cube <code>cargo-cube</code>] example included in the Rust-for-Dreamcast repo demonstrates creating a rotating 3D cube using Rust as the primary language, while calling C functions provided by the '''GLdc''' library available in kos-ports. This project's initial setup is done the same as the above <code>cargo-hello</code> example.
 
We'll be using the GLdc graphics and libm math libraries, so we need to tell <code>cargo-dc</code> to link them in. To do this, we'll add a <code>build.rs</code> file to the root of the crate with the following code:
<syntaxhighlight lang="rust">
fn main() {
    println!("cargo:rustc-link-lib=GL");
    println!("cargo:rustc-link-lib=m");
}
</syntaxhighlight>
 
We don't need to add the paths to these libraries, because adding the common paths to KallistiOS libraries is already done for us in the kos-rs crate. However, if in your project you need to include a separate unique library path, you can do that like so:
<syntaxhighlight lang="rust">
println!("cargo:rustc-link-search=native={}", lib_path);
</syntaxhighlight>
 
While not shown here, in the example our <code>build.rs</code> also demonstrates how to use a build script to convert JPG images to VQ-compressed textures with the <code>vqenc</code> tool included with KallistiOS. These texture files are then included in our project using the <syntaxhighlight lang="rust" inline>include_bytes!</syntaxhighlight> macro.
 
The workings of this example's source code are too great to detail here line-by-line, but the example demonstrates declaring and binding external C functions, constants, and structures and then using them in Rust code. Since the entirety of the example is C interop, the <code>main()</code> source is wrapped in <code>unsafe {}</code>. In the future, this would be much less necessary as higher level safe Rust bindings to KallistiOS and other libraries become mature.
 
==Creating a Rust library==
Next, we'll demonstrate creating a Rust library with <code>cargo-dc</code> that can be included in other Dreamcast code. This will follow the [https://github.com/darcagn/rust-for-dreamcast/tree/master/examples/cargo-addlib <code>cargo-addlib</code>] example included in the Rust-for-Dreamcast repo. Once again, this project's initial setup is done the same as the above <code>cargo-hello</code> example, but you'll create the new project using <code>cargo-dc new --lib addlib</code> to specify that we're creating a library named <code>addlib</code>. You'll also need to add the following text to this project's <code>Cargo.toml</code> file:
 
<syntaxhighlight lang="toml">
[lib]
crate-type = ["staticlib"]
</syntaxhighlight>
 
This tells Rust to build a static <code>.a</code> library archive file from our code, which is located in <code>src/lib.rs</code>:
 
<syntaxhighlight lang="rust" line>
#![no_std]
extern crate alloc;
use kos::print;
 
#[no_mangle]
pub extern "C" fn print_added(a: isize, b: isize) {
    print!("{}", a + b);
}


Clone the rustc_codegen_gcc to your rust directory:
#[no_mangle]
git clone https://github.com/rust-lang/rustc_codegen_gcc.git /opt/toolchains/dc/rust/rustc_codegen_gcc
pub extern "C" fn add_integers(a: isize, b: isize) -> isize {
Set the <code>gcc_path</code> file:
    a + b
echo /opt/toolchains/dc/rust/sh-elf/lib > /opt/toolchains/dc/rust/rustc_codegen_gcc/gcc_path
}
Copy the architecture configuration file, <code>sh-elf.json</code>, to its place:
</syntaxhighlight>
cp /opt/toolchains/dc/rust/misc/sh-elf.json /opt/toolchains/dc/rust/rustc_codegen_gcc/sh-elf.json
 
* patch out duplicate symbols in src/context.rs
The source code here starts similarly to the "Hello, world!" example, except we don't need to specify <syntaxhighlight lang="rust" inline>#![no_main]</syntaxhighlight> as this is a library which wouldn't have a <code>main()</code> function anyway.
* add SH4 precision mode to src/base.rs
 
* patch build_system/src/config.rs to alter default linker call
Two simple functions are provided: one for adding two integers and returning the result, and another for adding two integers and printing the result as text. Because these functions use <syntaxhighlight lang="rust" inline>#[no_mangle]</syntaxhighlight> and are declared <syntaxhighlight lang="rust" inline>extern "C"</syntaxhighlight>, they can be called by name in C code that links this library.
* patch build_sysroot/Cargo.toml to remove std and test from being pulled in
 
* y.sh clean
When built using <code>cargo-dc build</code>, a <code>target/sh-elf/debug/libaddlib.a</code> file will be generated. This can be linked into other projects to gain the use of these functions.
* y.sh prepare --cross
 
* y.sh build
For example, this can be added to a standard <code>Makefile</code>-based KallistiOS project by editing the <code>Makefile</code>:
<syntaxhighlight lang="make">
$(TARGET): $(OBJS)
kos-cc -o $(TARGET) $(OBJS) -L/opt/toolchains/dc/rust/examples/cargo-addlib/target/sh-elf/debug -laddlib
</syntaxhighlight>
 
Then, we can use the code in our C source:
<syntaxhighlight lang="c">
/* Declare the external function from the Rust library */
int add_integers(int a, int b);


==Creating a new project using Cargo==
/* Use the function */
* create an empty staticlib crate with target in .cargo/config
printf("Five plus six is %d\n", add_integers(5, 6));
* add kos-rs crate from /opt/toolchains/dc/rust/kos-rs path
</syntaxhighlight>


==Compiling individual modules into object files with rustc==
==Compiling individual modules into object files with rustc==
If we'd like to mix C and Rust code in the same <code>Makefile</code>-based KallistiOS project without building an entirely separate library, we can do that as well. This is demonstrated in the [https://github.com/darcagn/rust-for-dreamcast/tree/master/examples/rustc-hello <code>rustc-hello</code>] example included in the Rust-for-Dreamcast repo.
Instead of using <code>cargo-dc</code>, we can invoke the <code>rustc-dc</code> script in our <code>Makefile</code> to build Rust modules. If we assume the Rust module file is named <code>example.rs</code>, you'll need to add <code>example.o</code> as an object file in your <code>Makefile</code>'s <code>OBJS =</code> declaration. For example, if the project has two source files <code>hello_c.c</code> and <code>hello_rust.rs</code>, our <code>Makefile</code> would have a line like this:
<syntaxhighlight lang="make">
OBJS = hello_c.o hello_rust.o
</syntaxhighlight>
Additionally, you'll need to add the following lines so that <code>make</code> knows how to compile Rust modules into <code>.o</code> object files:
<syntaxhighlight lang="make">
%.o: %.rs
rustc-dc $< -o $@
</syntaxhighlight>


=gccrs=
The example code demonstrates starting a C <code>main()</code> function to call a Rust function which builds a <code>String</code> containing the "Hello, world!" text which is passed back to a C function which prints <code>String</code>s.
gccrs implements a Rust compiler frontend for GCC. This essentially means implementing a 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 <code>libcore</code>, so many basic language features are unimplemented or not functional. Additionally, Rust standard tooling like <code>cargo</code> is not available, nor is borrow checking implemented. It is possible to use this compiler by compiling either GCC 14.0.1-dev or GCCRS git repo.


==Building a GCCRS-enabled toolchain==
==Adjusting build settings==
==Setting up Makefiles to compile Rust modules==
Build settings can be adjusted through the <code>CG_GCCFLAGS</code> and <code>CG_RUSTFLAGS</code> environment variables. For example, invoking cargo like so:
CG_GCCFLAGS="-freorder-blocks-algorithm=simple" cargo-dc build
will build the project while passing along the <code>-freorder-blocks-algorithm=simple</code> optimization setting to the GCC backend. <code>rustc-dc</code> will also pass through these settings. If you wish to adjust the default flags passed to GCC, they are specified in the <code>common.sh</code> file.
If you wish to have <code>cargo-dc</code> pass through <code>RUSTFLAGS</code> arguments to the <code>rustc</code> compiler frontend, you can do so by using the <code>CG_RUSTFLAGS</code> environment variable.

Latest revision as of 16:42, 7 May 2024

Ferris holding his Dreamcast controller

Rust is a systems programming language rising in popularity which emphasizes memory safety and performance. Due to its operating at a low level, it is an ideal candidate for running on the Dreamcast. Doing so presents 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 typically done with GCC, the GNU Compiler Collection. There are currently two viable solutions to this challenge:

  • rustc_codegen_gcc: A libgccjit-based codegen backend for rustc (preferred method)
  • gccrs: a Rust frontend for GCC

While neither solution is complete at this time, rustc_codegen_gcc is much further along and is quite usable with some patience with its current limitations and rapid change. On the other hand, gccrs can compile for Dreamcast, but is in a very early stage, with much of the language unimplemented and no libcore support. Below we will focus on using rustc_codegen_gcc. For more information on using gccrs, see the gccrs page.

Building rustc_codegen_gcc to develop on Dreamcast

With rustc_codegen_gcc, we can interface the standard rustc compiler frontend with libgccjit, a GCC code-generation API. With the help of the Rust-for-Dreamcast repo and the kos-rs crate containing some early basic KallistiOS bindings, we can set up rustc_codegen_gcc to compile Rust programs with core and alloc support (but not the entirety of std). Rust-for-Dreamcast includes wrapper scripts to invoke the rustc and cargo tools in a familiar way. The familiar borrow checker still works, and one can import and use no_std crates. Despite this support, rustc_codegen_gcc is still in active development, so if using such a setup, expect that things may change rapidly over time. We will need to use some provided patches and scripts to make this solution work. See the rustc_codegen_gcc progress reports for more information on the upstream project's progress.

What Works

  • libcore -- the core components of the language for running on bare metal (basics like integers, floats, enums, bools, chars, tuples, arrays, slices, closures, iterators, etc.)
  • liballoc -- the core components of the language that require a heap, including collections (Vec, String, Box, etc.)
  • linking to KallistiOS -- KallistiOS and kos-ports can be used if one manually manages interoperating with C via unsafe
  • including no_std crates with the cargo build system

In Progress Now

  • libc support -- Adding KallistiOS support to Rust's libc crate
  • libstd support -- built-in language support for I/O, networking, threads, time and date, HashMap/HashSet, unwinding on panic, etc.

Future Goals

  • KallistiOS bindings -- properly idiomatic Rust support for KallistiOS
  • Upstream libc/libstd support, inclusion as a tier 3 target officially
  • Expansion of cargo-dc to support more dcdev-specific functionallity like generating Dreamcast disc images using metadata specified in Cargo.toml

Prerequisites

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 installed the typical dependencies, you have created a cross-compiling toolchain for SH4, you have set up your KallistiOS environ.sh file, and you have built KallistiOS with it. Ideally, you will already have at least some familiarity with KallistiOS dev already. See Getting Started with Dreamcast development for more information, as well as the KallistiOS Doxygen.
    • 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. Some included scripts and examples may assume this.
    • Your KallistiOS installation will need its KOS_SH4_PRECISION setting set to -m4-single. At this time, rustc_codegen_gcc support will not compile with KallistiOS's default -m4-single-only setting. This setting can be changed in KallistiOS's environ.sh, but changing the setting may require you to rebuild your toolchain if you have not built it with m4-single support (which is off by default, but can be enabled in the config.mk file). Once you modify the setting in your environ.sh and re-source the environ.sh, you'll need to rebuild KallistiOS with a make clean and make for the changes to take effect. kos-ports being used will also need rebuilding with -m4-single. Keep in mind, however, that because KallistiOS doesn't officially support -m4-single yet, some things may be broken, especially libraries in kos-ports that haven't been heavily tested with this setting.
  • You must already have a relatively up-to-date Rust installation using rustup. Ideally, you will already have some familiarity with Rust's tools.
  • Install the jq and xxd packages for your operating system. xxd might be part of vim depending on the organization of your operating system's package manager.

If you run into any errors or other challenges while following this tutorial, or simply need clarification on any of the steps, feel free to ask for assistance on the message board and we would be happy to aid you and update the guide for the benefit of future readers and others in the community.

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. The forked version is based on the latest GCC 14 development branch.

  • NOTE: This forked version of GCC 14 with libgccjit changes is actively developed alongside rustc_codegen_gcc itself, so if you update your rustc_codegen_gcc installation later, you may also need to rebuild libgccjit to pull down new changes rustc_codegen_gcc depends upon as well.

We will first clone the Rust-for-Dreamcast repository, which contains various supporting files needed to create Rust support for Dreamcast. Using git, clone the 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

Make any desired changes to thie Makefile.cfg configuration file (e.g., change makejobs=2 to the number of CPU threads you'd like to use during compilation), and then compile the SH4 toolchain:

make toolchain_profile=rustc sh_toolchain_path=/opt/toolchains/dc/rust/sh-elf enable_libgccjit=1

When this command is completed successfully, a new SH4 cross-compiler toolchain will exist at /opt/toolchains/dc/rust/sh-elf and your libgccjit.so will be installed to /opt/toolchains/dc/rust/sh-elf/lib/libgccjit.so.

Building rustc_codegen_gcc

Now that we have libgccjit built, we can use rustc_codegen_gcc to interface with it to generate SuperH machine code from Rust. Clone the rustc_codegen_gcc repository to your rust directory:

git clone https://github.com/rust-lang/rustc_codegen_gcc.git /opt/toolchains/dc/rust/rustc_codegen_gcc

rustc_codegen_gcc needs a config.toml file that specifies the location of libgccjit.so. Let's write the the gcc-path to the location of our libgccjit.so library file in this file:

echo 'gcc-path = "/opt/toolchains/dc/rust/sh-elf/lib"' > /opt/toolchains/dc/rust/rustc_codegen_gcc/config.toml

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. You'll need to add the path to those scripts to your PATH environment variable:

export PATH="/opt/toolchains/dc/rust/bin:$PATH"

You may also want to add the above line to your shell's startup file or else you'll need to re-run it every time you start a new shell.

Now we can use the included Rust-for-Dreamcast scripts to set up rustc_codegen_gcc. 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 we can prepare and build rustc_codegen_gcc!

rcg-dc prepare
rcg-dc build

Using Rust for Dreamcast

If all went well, rustc_codegen_gcc will have built successfully.

You can now use the scripts included in the Rust for Dreamcast repo:

  • the rcg-dc script can be used to rebuild the rustc_codegen_gcc code after updating or editing it
  • the rustc-dc script can be used to compile Rust modules
  • the cargo-dc script can be used to build Rust crates

Examples are included with the Rust for Dreamcast repo to help you get started:

  • cargo-hello demonstrates how to create a simple "Hello, world!" application with KallistiOS using cargo
  • cargo-cube demonstrates a Rust project using KallistiOS with GLdc
  • cargo-addlib demonstrates how to create a Rust library that can be included with a KallistiOS project
  • rustc-hello demonstrates how to compile and include a Rust module into a standard KallistiOS Makefile-based project

Creating a new Rust project with Cargo

First, we'll demonstrate creating a new "Hello, world!" project with cargo-dc. This will follow the cargo-hello example included in the Rust-for-Dreamcast repo.

The Cargo-based examples rely on the kos-rs crate being present on your computer locally. This is in a separate repo, so let's pull it down now:

git clone https://github.com/darcagn/kos-rs /opt/toolchains/dc/rust/kos-rs

In a directory of your choosing, let's invoke cargo-dc to create a new project and then enter the directory:

cargo-dc new hello
cd hello

Let's add our kos-rs crate to gain access to current KallistiOS bindings. Open Cargo.toml in your text editor and add:

[dependencies]
kos = { package = "kos-rs",  path = "/opt/toolchains/dc/rust/kos-rs" }

Now we can open up src/main.rs and write our "Hello, world!" example code:

#![no_std]
#![no_main]
extern crate alloc;
use kos::println;

#[no_mangle]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
    println!("Hello, world!");
    return 0;
}
  • #![no_std] and #![no_main] tell Rust that our project does not use the standard library and we will not have Rust use a main function as an entry point -- this will be handled by KallistiOS.
  • extern crate alloc; tells Rust to use the alloc crate to gain access to heap-allocated types (in our case, String).
  • use kos::println!; tells Rust to use the println! macro defined in the kos-rs crate. With this, we can print output to our dc-tool console.
  • #[no_mangle] tells Rust to disable name mangling so that the main function can be used by KallistiOS.
  • Finally, we have a fn main with the function signature of a typical C main function, containing a basic "Hello, world!" exclamation.

Now we can use cargo-dc build to build our project. If all goes well, there will be a target/sh-elf/debug/hello.elf file that can be sent to the Dreamcast with dc-tool. If you have KOS_LOADER set in your KallistiOS environment, you can invoke it directly with cargo-dc run.

Creating a Rust project using kos-ports libraries

cube example in action

The cargo-cube example included in the Rust-for-Dreamcast repo demonstrates creating a rotating 3D cube using Rust as the primary language, while calling C functions provided by the GLdc library available in kos-ports. This project's initial setup is done the same as the above cargo-hello example.

We'll be using the GLdc graphics and libm math libraries, so we need to tell cargo-dc to link them in. To do this, we'll add a build.rs file to the root of the crate with the following code:

fn main() {
    println!("cargo:rustc-link-lib=GL");
    println!("cargo:rustc-link-lib=m");
}

We don't need to add the paths to these libraries, because adding the common paths to KallistiOS libraries is already done for us in the kos-rs crate. However, if in your project you need to include a separate unique library path, you can do that like so:

println!("cargo:rustc-link-search=native={}", lib_path);

While not shown here, in the example our build.rs also demonstrates how to use a build script to convert JPG images to VQ-compressed textures with the vqenc tool included with KallistiOS. These texture files are then included in our project using the include_bytes! macro.

The workings of this example's source code are too great to detail here line-by-line, but the example demonstrates declaring and binding external C functions, constants, and structures and then using them in Rust code. Since the entirety of the example is C interop, the main() source is wrapped in unsafe {}. In the future, this would be much less necessary as higher level safe Rust bindings to KallistiOS and other libraries become mature.

Creating a Rust library

Next, we'll demonstrate creating a Rust library with cargo-dc that can be included in other Dreamcast code. This will follow the cargo-addlib example included in the Rust-for-Dreamcast repo. Once again, this project's initial setup is done the same as the above cargo-hello example, but you'll create the new project using cargo-dc new --lib addlib to specify that we're creating a library named addlib. You'll also need to add the following text to this project's Cargo.toml file:

[lib]
crate-type = ["staticlib"]

This tells Rust to build a static .a library archive file from our code, which is located in src/lib.rs:

#![no_std]
extern crate alloc;
use kos::print;

#[no_mangle]
pub extern "C" fn print_added(a: isize, b: isize) {
    print!("{}", a + b);
}

#[no_mangle]
pub extern "C" fn add_integers(a: isize, b: isize) -> isize {
    a + b
}

The source code here starts similarly to the "Hello, world!" example, except we don't need to specify #![no_main] as this is a library which wouldn't have a main() function anyway.

Two simple functions are provided: one for adding two integers and returning the result, and another for adding two integers and printing the result as text. Because these functions use #[no_mangle] and are declared extern "C", they can be called by name in C code that links this library.

When built using cargo-dc build, a target/sh-elf/debug/libaddlib.a file will be generated. This can be linked into other projects to gain the use of these functions.

For example, this can be added to a standard Makefile-based KallistiOS project by editing the Makefile:

$(TARGET): $(OBJS)
	kos-cc -o $(TARGET) $(OBJS) -L/opt/toolchains/dc/rust/examples/cargo-addlib/target/sh-elf/debug -laddlib

Then, we can use the code in our C source:

/* Declare the external function from the Rust library */
int add_integers(int a, int b);

/* Use the function */
printf("Five plus six is %d\n", add_integers(5, 6));

Compiling individual modules into object files with rustc

If we'd like to mix C and Rust code in the same Makefile-based KallistiOS project without building an entirely separate library, we can do that as well. This is demonstrated in the rustc-hello example included in the Rust-for-Dreamcast repo.

Instead of using cargo-dc, we can invoke the rustc-dc script in our Makefile to build Rust modules. If we assume the Rust module file is named example.rs, you'll need to add example.o as an object file in your Makefile's OBJS = declaration. For example, if the project has two source files hello_c.c and hello_rust.rs, our Makefile would have a line like this:

OBJS = hello_c.o hello_rust.o

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 $@

The example code demonstrates starting a C main() function to call a Rust function which builds a String containing the "Hello, world!" text which is passed back to a C function which prints Strings.

Adjusting build settings

Build settings can be adjusted through the CG_GCCFLAGS and CG_RUSTFLAGS environment variables. For example, invoking cargo like so:

CG_GCCFLAGS="-freorder-blocks-algorithm=simple" cargo-dc build

will build the project while passing along the -freorder-blocks-algorithm=simple optimization setting to the GCC backend. rustc-dc will also pass through these settings. If you wish to adjust the default flags passed to GCC, they are specified in the common.sh file. If you wish to have cargo-dc pass through RUSTFLAGS arguments to the rustc compiler frontend, you can do so by using the CG_RUSTFLAGS environment variable.