Porting the Hare language to DragonFly
The Hare programming language is a practical systems programming language with a focus on:
- fitting on one floppy disk (that’s 1.44 MiB),
- being around for the next 100 years,
- being “hackable” and simple enough to be understood by a single person without holding a PhD in formal systems or type theory,
- while being elegant and readable.
That’s my basic understanding of Hare’s goals. And now, tada, it’s also available for DragonFly, the underdog among the BSD operating systems.
Porting Hare to DragonFly was straightforward, especially when compared to the effort and time it took me to port Rust to DragonFly. Read on for further details about the porting process.
Porting the bootstrap compiler
The first step was to port harec. This is the Hare bootstrap compiler written
in ANSI C. You can obtain it from this location:
https://git.sr.ht/~sircmpwn/harec
The commit that adds support for DragonFly is the following:
https://git.sr.ht/~sircmpwn/harec/commit/8d848f258a7edbbb48e5890cc24507d40b243417
As the compiler is written in portable ANSI C, only the Makefile’s had to be
slightly adapted. A bit more effort was to port the minimal runtime called
“rt”. This library provides the interface to the DragonFly kernel via system
calls, so basically it is a replacement for libc. It is a mix of Hare and
assembly language and is kept to a minimum, just enough to be able to compile
the “real” Hare compiler, which is written in Hare itself.
The “rt” code for DragonFly is mostly a copy of the code for FreeBSD. The only
exception, apart from a few syscalls having different numbers, is that
DragonFly’s mmap system call takes 7 arguments, while it is just 6 arguments
for all the other operating systems. And also the pipe2 system call is
slightly different in that the DragonFly kernel does not touch the filedes[2]
array, instead it returns the two file descriptors in registers eax and edx.
The fact that mmap takes 7 arguments on DragonFly made it a bit more tricky.
The x86-64 System V calling convention only allows passing 6 arguments in
registers, so the additional 7th argument has to be passed via the stack. For
this, I first had to obtain a basic understanding of the System V system call
and function call conventions. Basically, you only need to know which argument
is passed in which register and which register is caller saved and which is
callee saved. Then you are good to go. Doing the register assignment or
“shuffling” in your head can be tricky, so I recommend writing things down on a
piece of paper.
After all, it was such a welcome change to get my hands dirty with assembly
code again after so many years have passed. At a much younger age, some 25
years ago, I even spoke machine language, which I taught myself while writing a
disassembler, if my mind doesn’t trick me. So at that time, I really could type
in the hex codes for 32-bit Intel ISA directly into an editor written in
assembly, which would then execute the machine code directly from memory. Hell,
that was fun and it really made me appreciate assembly language! 0xcd 0x90
anyone?
Once I got harec working, it was time for me to start porting the “real” Hare
compiler, hare.
Porting “The Real Thing”
(If you are old enough and from Europe you might remember “The Real Thing” which “makes your body sweat” from 2 Unlimited).
This is the Hare compiler written in Hare itself and also includes a full blown
standard library and some other tools such as haredoc, the documentation
generator. The source code is available here:
https://git.sr.ht/~sircmpwn/hare
At this point, I really want to take the chance and express my thankfulness to the Hare community, which maintains both the C and the Hare version of the compiler in parallel and keeps both versions up to date. This makes bootstrapping so much easier.
Porting hare to DragonFly was more effort, mainly because the runtime library
“rt” and the other parts of the standard library provide you with a complete
“libc” alternative out of the box without depending on libc. This, by the way,
is the same approach other languages like Go or Zig take. Luckily, it wasn’t
the first time I have done things like that - for instance I originally ported
Rust and it’s libc to DragonFly and also did the port of the Crystal
language.
Porting hare was an iterative process, heavily supported by the test-suite and
based on the existing code for FreeBSD. I picked the code for FreeBSD as
starting point mainly because DragonFly has it’s roots in FreeBSD, and even 20
years later, both operating systems still share most of it’s system calls and
data structures. Same same, but different. Most of the porting work was done by hand,
carefully reviewed by my eyes all of which was supported by some small Ruby scripts to
parse #define‘s and translate them into Hare code.
The commit that brings in support for DragonFly is the following one:
https://git.sr.ht/~sircmpwn/hare/commit/e69fd862122c95486a87d05bba66e5856b2693d8
Once I submitted the patch to the hare-dev mailing list, the first question I’ve been asked was: “did you use an LLM to create this patch?”, to which I, slighlty irritated, responded with “nope, or should I?”. The guy asking that question is likely unaware that my office door is decorated with a poster that says “old school” :). Thankfully, the response I got was “no please not :D”.
No further comment needed.
Future work
The commit message above mentions a few open tasks:
-
DragonFly does not yet ship with a
/usr/share/zoneinfo/leap-seconds.listfile which is required fortime::chronosupport. It can be obtained from NetBSD in the meanwhile -
DragonFly also does not ship with
/etc/mime.typesby default. But you canpkg install mime-supportwhich gives you/usr/local/etc/mime.types - There is also no CI support for DragonFly on sourcehut yet
So the next big step is to add support for DragonFly to the CI system of
sourcehut. This would give us immediate feedback on every git pull. This not
just helps anybody who wants to use Hare on DragonFly, but also is of great
help to the other Hare developers, who are kind enough to care not to
accidentially break DragonFly support while adding new functionality to
Hare.
Closing words
It was a pleasant experience to port Hare to DragonFly. Hare and it’s implementation stayed true to its goals and my expectations of being easy to work with and “hackable”. In that regard, I really love the language and it’s approach.
Let me close this article with an unspectacular Hello world example written in Hare:
use fmt;
export fn main() void = {
fmt::printfln("Hello {}", "world")!;
};
You can run the example either directly via hare run hw.ha or by first
compiling it with hare build hw.ha and then by running the executable hw. Without
going too much into detail here, if you look closely at the above example you might
notice three characteristics:
- It uses some kind of module system, similar to Go.
-
It does not silently hide errors. Did you notice the exclamation mark
!at the end offmt::printfln? -
It consistently terminates every statement with a semicolon
;. This might look strange at first sight, but likely simplifies the parser.
The way Hare handles errors is really worth a separate article. Stay tuned! Good night!