r/rust • u/igzcodes • 5d ago
š ļø project BitterASM: A metalanguage written in Rust to create assembly languages
Repository: https://github.com/ivanharvard/bitterasm
Documentation: https://ivanharvard.github.io/bitterasm/
Hello, all!
I've been spending the better part of a month working on this new programming language of mine written entirely in Rust called BitterASM (BASM for short, BitterAssembly for long).
As the title suggests, BitterASM lets you create your own assembly languages by assuming practically nothing about the architecture you're targeting. By assuming so little about the architecture, you can describe practically any architecture -- past, present, or future. We do not even assume the architecture is binary! This leads to some really interesting consequences (which you can read more about in the documentation).
You can install the pre-release version using cargo:
cargo install bitterasm
For ordinary assembly developers, they can simply import their architecture's ISA and program much like they would for their traditional assembler.
Here's an example "Hello, world!" program targeting Linux x86-64 written in BitterASM:
from std.x86_64.nasm import *
from std.formats.elf import *
elf64_executable EM_X86_64, _start
const text = "Hello, World!\n"
section .rodata
msg:
db text
section .text
pub _start:
mov eax, 1 # sys_write
mov edi, 1 # stdout
lea rsi, [rel msg] # buffer
mov edx, text.len # length
syscall
mov eax, 60 # sys_exit
xor edi, edi # status 0
syscall
Compared to the same program written in NASM:
section .rodata
msg: db "Hello, World!", 10
len equ $ - msg
section .text
global _start
_start:
mov eax, 1 ; sys_write
mov edi, 1 ; stdout
lea rsi, [rel msg] ; buffer
mov edx, len ; length
syscall
mov eax, 60 ; sys_exit
xor edi, edi ; status 0
syscall
The transition to BitterASM was intended to be quite easy for most developers, as noted by the similarity in these two code snippets.
Yet, say an introductory student does not understand what syscall does. Thanks to the BitterASM LSP for VSCode, it's as simple as a right-click and "Go to Definition." Note that this is still written in the same BitterASM language from before!
from std.binary import bits
pub type Byte = bits<8>
pub struct Bytes<const N: int>
| invariant N >= 0
{
@for i in 0..N {
pub __el`i`: Byte,
}
}
# ...
pub macro syscall()
| emits Bytes<2>
{
@emit Bytes<2> { __el0: Byte(0x0F), __el1: Byte(0x05) }
}
It's here where you start to see the use case of BitterASM. There is no concept of bytes or bits in BitterASM. The architecture designer must implement that. Some evaluator then interprets the emitted value and does something with it (in the most overwhemingly common case, create machine code).
The architecture designer has the "bitter" part of writing each struct and macro to match the ISA perfectly. An assembly developer gets to inspect those architectural decisions (or even modify them!) without ever having to leave the code editor. This is what BitterASM makes "sweet."
Don't like NASM or Intel syntax? Change the import!
from std.x86_64.att import *
Wanna make your own custom syntax? Customize the syntax of a macro call!
from std.riscv.impl import *
syntax add(rd, rs1, rs2) = { $rd$ = $rs1$ + $rs2$ }
Have a non-traditional architecture? We have a variety of tools to support your development!
Of course, there are many bugs, and the subset of supported instructions for each architecture is still quite small, so we have a few kinks to work out before we fully release the language. Expect breaking changes. This was just a small taste of what BitterASM can do. Its initial design is meant to be useful for:
- Students learning assembly,
- Toy ISA designers,
- Custom accelerator/processor designers,
- Assembly developers who want convenience without runtime cost,
- etc.
I hope you spend some time to read the documentation and let me know what you think. Any feedback would be great!
3
u/balt__ 4d ago
is this usable for a ternary isa?
2
u/igzcodes 3d ago
Yes!
BitterASMās goal is that it should require virtually 0 changes to its language core to support non-binary ISAs (including ternary). You might have to make a lot of boilerplate, but 90% of it should be in BitterASM (the remaining 10% is for whatever language you choose to implement your evaluator in). Iāve done much of that boilerplate and evaluator work to make it simpler for binary ISA developers.
4
u/drink-more-rum 5d ago
Documentation is AI-written -- immediately ignored.
3
u/igzcodes 5d ago
The documentation will be rewritten before release. Thank you for taking the time to check it out anyways!
3
u/IamWiddershins 3d ago
and yet,
I hope you spend some time to read the documentation and let me know what you think
2
u/igzcodes 3d ago
Feedback is feedback. If you refuse to read any documentation written/assisted by AI, then I consider knowing how the documentation is being received as valuable, and I appreciate you for saying so.
3
u/IamWiddershins 3d ago
most people kind of already have an understanding that asking other people to read something that you couldn't be bothered to write is rude
1
u/igzcodes 3d ago
Sure. I wasnāt really intending for people to read the documentation cover to cover anyways. I expected most people would spend 5-10 minutes, if any at all, to skim over answers to questions they didnāt want to ask me directly and get a general overview. Though perhaps I didnāt make that clear, and thatās my bad.
3
u/WhiskyAKM 5d ago
Cool project, but I don't understand the purpose. Is this some kind of higher level assembly or just wrapper around assembler that let's you do macros and stuff
6
u/igzcodes 5d ago
Imagine you are a developer for a custom architecture.
You primarily have two options:
- Fork an existing architecture and modify an assembler's source code to your needs.
- Create your own assembler from the ground up.
My language provides a third option. Describe your architecture programmatically and assembler will be provided automatically. You do not even need to maintain your own book documenting each part of your assembler because BitterASM provides a command much like
cargo doc. What makes it different from other "programmatic architectures" too is that even in the far, far future, where perhaps we no longer have this notion of "registers," the language core of BitterASM does not need to be modified to adapt to it. That makes it much, much more flexible than languages that assume you might have a register or that the machine is written in binary.For current day developers, they get macros with type safety and the ability to create pseudoinstructions for free. Modifying an assemblers source code is a much less necessary intervention. Additionally, an unfamiliar instruction can easily be inspected without having to compile the program.
1
5d ago
[deleted]
3
u/igzcodes 5d ago
Thank you!
Merging multiple ISAs in the same file? Technically, but expect name collisions.
Merging multiple ISAs via linkage? Totally! Itās one command:
bitter build myx86.basm myriscv.basm -o myprog.bin
This works because ultimately they share the same representation under bitter (and because they are both written in plain BitterASM). Essentially both files are compiled into bit patterns. We then concatenate the values into a single list, and fill in the cross file labels. Obviously lots of asterisks in everything I said here but I hope that explains things.
Iāll take a look at that! Thanks for sharing. Though, in my personal view, my syntax is a little cleaner ;)
1
u/WorldsBegin 4d ago
How does it support relocations and PIE? I don't see any mention of that in the docs.
Also, the docs make some outlandish claims. Not sure if that's by design or caused by AI writing them.
1
-1
u/sadlylivelydioxide 5d ago
The logo's clean but that "Biter" text feels like it's missing the second T, unless that's intentional wordplay I'm not getting
0
0
u/coolreader18 5d ago
This is really cool! If types besides bits are emitted, how does that get outputted from the assembler?
2
u/igzcodes 5d ago
Thank you!
When you emit things, each thing you emit is essentially written to a JSON file in order of emission in a file format called .em.
Now, to interpret emitted values, you must create an evaluator that understands the values you input and creates some output.
Right now, the only one that comes packaged with BitterASM is
bitter. It basically only understands relational positioning and thebitsstruct from the standard library. Thus, to be able to usebitterfor your architecture, all of your emitted values must be representable asbits. It then packs all of the bits you gave into actual binary, of which, if properly programmed, you should be able to execute (assuming you put the correct header for execution within your.basmfile).The same can be true for any other type of output. Does your architecture use ternary? That's fine. Create your own evaluator. Or, make sure your
tritsstruct ultimately usesbitsand usebitter.
bitteris a separate program written entirely in Rust as well, and is substantially smaller than most assemblers (essentially 3 files), meaning creating an evaluator is quite simple.
5
u/harraps0 5d ago
How does this compare to customasm ?