I. Targets: Ready... Aim...
If we tried to compile something right now, Rust would probably spit out an x86 ELF/Mach-O/PE executable. It wouldn't run for a few reasons:
- The PlayStation 2 doesn't understand these formats; it'd just try to execute it as a binary blob and trip up on their magic numbers.
- Even if it did understand these files, they would be targeted for the wrong architecture.
Rust needs to be told how to emit code for the PS2. For that we need to define a target file.
Target files
rustc
includes its own target files for each architecture; you can look at the available targets
with the following command:
rustc --print target-list
Each target file is in a JSON format, that you can inspect with the following command:
rustc -Z unstable-options --print target-spec-json --target $target
For the EE, we'll start off the mipsel-unknown-linux-gnu
target, which looks like this:
{
"arch": "mips",
"cpu": "mips32r2",
"data-layout": "e-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64",
"dynamic-linking": true,
"env": "gnu",
"executables": true,
"features": "+mips32r2,+fpxx,+nooddspreg",
"has-elf-tls": true,
"has-rpath": true,
"is-builtin": true,
"linker-flavor": "gcc",
"linker-is-gnu": true,
"llvm-target": "mipsel-unknown-linux-gnu",
"max-atomic-width": 32,
"os": "linux",
"position-independent-executables": true,
"pre-link-args": {
"gcc": [
"-Wl,--as-needed",
"-Wl,-z,noexecstack"
]
},
"relro-level": "full",
"target-c-int-width": "32",
"target-endian": "little",
"target-family": "unix",
"target-pointer-width": "32",
"vendor": "unknown"
}
This contains a lot of unnecessary (and inaccurate) things, such as this target being for MIPS32r2. Let's change it a bit.
{
"arch": "mips",
"cpu": "mips2",
"data-layout": "e-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64",
"dynamic-linking": false,
"executables": true,
"features": "+mips2",
"linker": "mipsel-none-elf-ld",
"linker-flavor": "ld",
"llvm-target": "mipsel-none-elf",
"llvm-args": "-mxgot",
"max-atomic-width": 32,
"os": "none",
"panic-strategy": "abort",
"position-independent-executables": false,
"relro-level": "full",
"soft-float": true,
"target-c-int-width": "32",
"target-endian": "little",
"target-family": "unix",
"target-pointer-width": "32",
"vendor": "unknown"
}
Some important changes:
"cpu": "mips2"
- We need LLVM to target the MIPS II instruction set."soft-float": true
- The R5900 has a single-float FPU, which LLVM has quite a few bugs with, so we pretend it doesn't have one to work around them."linker": "mipsel-none-elf-ld"
/"linker-flavor": "ld"
- We will need to use the GNU linker to build this, because LLD seems to have a nasty habit of optimising out our code.
The correct settings here would be
"cpu": "mips3"
for the R5900 and"cpu": "mips1"
for the R3051, but as mentioned previously, LLVM support for these needs to mature.
I will refer to this target file as ee.json
, and you should put it in your crate/workspace root.
Building cross binutils
This isn't directly Rust related, but we need a linker for our code, and binutils has proven to be very reliable in my experiments. One day, I hope LLD is stable enough to use.
You'll need a GNU-compatible host C compiler (gcc
/clang
will do fine, but not MSVC++), and a
copy of the binutils source. I'm using binutils 2.31.
After extracting your source, you can build it with a standard-ish method:
mkdir build
cd build
../configure --target="mipsel-none-elf"
make
sudo make install
And then you can test it installed correctly by running mipsel-none-elf-ld --version
.
One final thing
To get rustc
to build for a native target, we use cargo build
; but Cargo doesn't currently
work well with cross-compilation, because it expects the various libraries to be already installed.
This may change with std-aware Cargo.
We can get around this through the cargo-xbuild
wrapper, which you can grab with a simple cargo install cargo-xbuild
. This allows you to build your code with cargo xbuild --target ee.json
, and
also wraps Clippy.
Don't forget the
.json
for--target
; I had some problems where it would build your code fine without it (i.e.--target ee
), but fail to build any library crates your code depended on.