2015-05-08

Translating the Program to Rust

Last time, I announced my new goal: Programming my Arduino Due with Rust. Getting started with that turned out to be surprisingly easy. Since the C program doesn't have any dependencies and consists only of a single file, there was only one problem to solve: Translating the program to Rust and figuring out how to build that.

Before we take a look at the new program, two notes:

  1. I've deleted the old C program from the repository, but thanks to the wonders of version control, you can always access the last version of the C program. It might be helpful to refer back to it while reading the article, to compare the old C code with the new Rust code.
  2. From here on out, I'm going to assume a basic familiarity with Rust, just like I previously assumed a basic familiarity with C. I'll attempt to explain the weird stuff, but I will just take it for granted that you can understand "normal" Rust code. If you don't know Rust but still want to follow along, the official documentation is a good place to start learning.

Okay then, without further ado, let's get to the program!

#![feature(intrinsics, lang_items, no_std)]
			

Before we're doing anything else, we need to opt into a few language features that we're going to use. While the Rust developers promise not to break compatibility in the stable subset of the language[1], we need some unstable features for our use case, which requires an explicit opt-in.

This also means that our program will only work with a nightly version of Rust, since unstable features are not available in stable releases.

#![no_main]
#![no_std]
			

Here we're telling the compiler two things.

extern "rust-intrinsic" {
	fn overflowing_add(a: T, b: T) -> T;
	fn u32_sub_with_overflow(x: u32, y: u32) -> (u32, bool);
}
			

Here we're declaring some intrinsic functions that are provided to us by the compiler. Rust relies a lot on library code to provide even basic functionality and, by itself, is even more limited than C in a lot of ways. Specifically, even basic operators like + and < are not available without building some basic infrastructure first.

For now, we're working around this problem by not using those operators and relying on intrinsic functions instead.

#[lang = "copy"]
trait Copy {}
#[lang = "sized"]
trait Sized {}
#[lang = "sync"]
trait Sync {}
			

This is another example of Rust not really being able to do anything without basic infrastructure. Here we're defining a few traits that are used to mark types.

None is this is particularly relevant to our program, but those concepts are so basic to Rust that the compiler won't be able to compile a program without them being defined. Since we said above that we don't want the compiler to link the standard library, we need to provide this kind of stuff ourselves.

#[no_mangle]
pub fn __aeabi_unwind_cpp_pr0() {
	loop {}
}
			

I'm not so sure what this function is, but the Rust compiler writes references to it into the executable, so if it isn't defined somewhere, the program won't link. I think this is related to stack unwinding, a thing that needs to happen if the program panics, that is, encounters an unrecoverable[3] error.

For our purpose, this stub implementation that just loops forever is more than enough[4]. At some point we'll need better error handling, but let's not get ahead of ourselves.

// This is the top of the stack, as provided to us by the linker.
extern {
	static _estack: u32;
}
			

So much for weird Rust-specific stuff. This is something we recognize from our C program! From here on out, everything will be, more or less, a straight translation.

pub struct VectorTable {
	pub initial_stack_pointer_value: &'static u32,
	pub reset_handler              : fn(),

	pub other_interrupt_vectors: [u32; 44],
}

impl Sync for VectorTable {}
			

The definition of the vector table type. The only difference to the C code is that the types are a bit more strict. This wasn't required in C, as C allows us to just cast one type to another willy-nilly. Rust is a bit more strict, hence the more accurate types.

Since we're going to save the vector table in a global variable, we need to implement the Sync trait for it, thereby promising the Rust compiler that it'll be safe to access it from multiple threads.

// The vector table. We're telling the compiler to place this into .vectors
// section, not where it would normally go (I suppose .rodata). The linker
// script makes sure that the .vectors section is at the right place.
#[link_section=".vectors"]
pub static VECTOR_TABLE: VectorTable = VectorTable {
	initial_stack_pointer_value: &_estack,
	reset_handler              : start,
	other_interrupt_vectors    : [0; 44],
};
			

Safe for the slightly different syntax, this is exactly the same as it was in the C program.

// Addresses of several registers used to control parallel I/O.
const PB_PIO_ENABLE       : *mut u32 = 0x400E1000 as *mut u32;
const PB_OUTPUT_ENABLE    : *mut u32 = 0x400E1010 as *mut u32;
const PB_SET_OUTPUT_DATA  : *mut u32 = 0x400E1030 as *mut u32;
const PB_CLEAR_OUTPUT_DATA: *mut u32 = 0x400E1034 as *mut u32;

// Bit mask for PB27. This is pin 13 (the built-in LED) on the Arduino Due.
const PB27_MASK: u32 = 0x08000000;

// Addresses of several registers used to control the real-time timer.
const TIMER_MODE_REGISTER : *mut   u32 = 0x400E1A30 as *mut   u32;
const TIMER_VALUE_REGISTER: *const u32 = 0x400E1A38 as *const u32;
			

More straight-forward translation. The only thing to note is that those *mut and *const pointers are not the pointer types we would use in regular Rust code. Instead, they are so-called unsafe pointers, specifically meant for interacting with C code (or in this case, low-level hardware).

fn sleep_ms(milliseconds: u32) {
	unsafe {
		let sleep_until = overflowing_add(
			*TIMER_VALUE_REGISTER,
			milliseconds
		);

		let mut sleep = true;
		while sleep {
			let (_, overflow) = u32_sub_with_overflow(
				sleep_until,
				*TIMER_VALUE_REGISTER
			);
			sleep = !overflow;
		}
	}
}
			

The sleep function has changed a bit, and not for the better. As I explained above, we can't use basic operators yet, so I had to get a bit creative with intrinsic functions. We'll clean this up later.

fn start() {
	unsafe {
		// Enable PB27 (pin 13) and configure it for output.
		*PB_PIO_ENABLE    = PB27_MASK;
		*PB_OUTPUT_ENABLE = PB27_MASK;

		// Set the timer to a resolution of a millisecond.
		*TIMER_MODE_REGISTER = 0x00000020;

		// Continuously set and clear output on PB27 (pin 13). This
		// blinks the Due's built-in LED, which is the single
		// purpose of this program.
		loop {
			*PB_SET_OUTPUT_DATA = PB27_MASK;
			sleep_ms(200);
			*PB_CLEAR_OUTPUT_DATA = PB27_MASK;
			sleep_ms(800);
		}
	}
}
			

And the start function. Again, pretty much the same as it used to be in C. The main difference is that Rust considers our dereferencing of unsafe pointers to be an unsafe operation. Hence, the unsafe block, which is just a promise to the compiler that we paid really close attention to what happens within the block and that it can treat the start function as safe, even though it contains unsafe code.

The build command is pretty straight-forward:

rustc \
	--target=target.json \
	-C link-args="-Tlinker-script.ld" \
	-o blink.elf \
	blink.rs
			

The command refers to the file target.json which contains all the nasty platform-specific details[5]:

{
	"data-layout"         : "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:64:128-a:0:64-n32",
	"llvm-target"         : "thumbv7m-unknown-none-eabi",
	"linker"              : "arm-none-eabi-gcc",
	"target-endian"       : "little",
	"target-pointer-width": "32",
	"arch"                : "armv7-m",
	"os"                  : "none",
	"executables"         : true
}
			

Some of it is straight-forward, some of it looks like black magic. Following established tradition, I'm not going to fret too much about this for now. My goal remains to understand what's going on with my program running on the hardware. Whatever sorcery is required to build the program in the first place is only of secondary concern[6].

The rest of the build process has, yet again, remained unchanged. I'm not going to repeat it any more.The full build script is available in the repository.

One piece is still missing. The Rust compiler tries to link to a file called libcompiler-rt.a. If it's not available, the build fails. I did a bit of research and identified this as the compiler-rt Runtime library.

In a bit of sly thinking that I'm quite proud of, I thought that, since I'm not using anything from that library (that I know of), maybe I can just create an empty library to shut the compiler up? Turns out I can!

ar m libcompiler-rt.a
			

Using the ar command, you can create an empty library, which, for now, is enough to satisfy the Rust compiler.

And that's it for today. As always, the full code is available on GitHub. See you next time!