Writing Rust for the Atari Mint
Recently I decided to get into a retrocomputing mood. I watched Matt Godbolt’s videos on Computerphile talking about how computers work and after listening to him talk about learning how to program on these old computers, I was ready to learn more. I was ready to open my wallet and buy a retrocomputer and test out programming on these computers. On ebay an Atari ST costs about $200. I reached into my pocket to grab my credit card to buy one before coming to the realization that I’m broke. Luckily, there’s a pretty active community programming these retro computers, complete with emulators. I found a couple emulators like aranym hatari and went on my way.
You could write code for these emulators in assembly or C, due to there being a gcc toolchain for them: https://tho-otto.de/crossmint.php. I downloaded gcc, hooked up binutils and libraries, and started to write some code.
I went to go write some simple coreutils for the emulator, but
compiling my first binary (rm), my binary was >100KB.
Given that the atari ST was supposed to run on 1.44MB floppies, eating
up a little over 1/15th of your storage on a 50-line C program is a
little absurd.
It turns out that the libc that the Atari mint toolchain
ships with links to a library called mintlib, which is a
pretty complete libc, but its binaries are huge. There’s an
alternative libc which isn’t as complete, called
libcmini. I linked to that libc instead and
saw the familiar file size of 4KB. Nice.
However, the binaries are still pretty large if you think about it. An assembly version of this would probably fit in a few hundred bytes, so C in this case does have a pretty large penalty.
I decided to start writing my own minimalistic libc and
incrementally build out some parts of it. The first part was the C
runtime, crt0.S. Afterwards, writing initializers and
finalizers, setting up the stack, env vars, and jumping to start. All of
this was pretty tricky but doable. Some interesting bits were the system
calls: mintbind.h
where, given this isn’t a Linux OS, a lot of the system calls are pretty
interesting – there’s even a “malloc” so you can delegate memory
management to the kernel.
While writing the libc was fun to see what was under the hood, I
wanted some higher-level code. So I decided I wanted to write Rust.
First things first – I downloaded the a.out toolchain from
Thorsten Otto’s website. I needed the ELF one, since that was what
rust’s toolchain supported. Next, I had to write up a target.json
file, describing the properties of the architecture I was
targeting.
{
"arch": "m68k",
"atomic-cas": false,
"cpu": "M68040",
"crt-objects-fallback": "false",
"crt-static-default": false,
"crt-static-respected": false,
"code-model": "large",
"data-layout": "E-m:e-p:32:16:32-i8:8:8-i16:16:16-i32:16:32-n8:16:32-a:0:16-S16",
"dynamic-linking": true,
"eh-frame-header": false,
"executables": true,
"has-rpath": true,
"has-thread-local": true,
"linker": "m68k-atari-mintelf-gcc",
"llvm-target": "m68k-unknown-elf",
"linker-is-gnu": true,
"linker-flavor": "gcc",
"pre-link-args": {
"gcc": [
"-nostartfiles",
"-nostdlib",
"-m68000",
"-no-pie"
]
},
"late-link-args": {
"gcc": [
"-Wl,--gc-sections",
"/home/takashi/current/mintbox/build/objs/crt0.o",
"-Wl,--start-group",
"-L/home/takashi/current/mintbox/build",
"-lcbox",
"-Wl,--end-group",
"-lgcc"
]
},
"max-atomic-width": 32,
"os": "mint",
"panic-strategy": "abort",
"position-independent-executables": false,
"relocation-model": "static",
"static-position-independent-executables": false,
"target-endian": "big",
"target-mcount": "_mcount",
"target-pointer-width": 32,
"vendor": "atari"
}After quite a bit of fumbling around, reading docs online, reading
the forums I was able to compile some code for rust. However, I ran into
the same problem where my binaries were 100KB. I couldn’t link to
libcmini, however, since I wanted to link to alloc in my
no_std Rust code. To do so, I needed an allocator, which I wanted to
delegate to the libc for. However, this requires
posix_memalign, which libcmini does not
provide. I implemented it in my libc, and was off to the races.
However, Rust turned out to be a pain in a very different way than
writing C. If you try to implement printing with Rust’s
fmt helpers when you try to print a type that can’t be “inlined” by
the compiler, it’ll pull in compiler builtins, and bundle a 300KB
runtime, bloating your binary by that much. If you for example, try to
debug print an integer, that’s 300KB. There might be some workaround by
using args.as_str() and failing, but this didn’t work in my
case. So I decided to stop here. But it was a fun experiment.