DEV Community

YJDoc2
YJDoc2

Posted on

A Rust macro which will derive Bytecode for you

Hello!

I made a Rust proc-macro which will compile and parse your enums and structs to and from a bytecode for you!!!

GitHub logo YJDoc2 / Bytecode

A Rust proc-macro crate which derives functions to compile and parse back enums and structs to and from a bytecode representation

Check it out and star it if you think it is interesting!

Why

Consider you are writing a VM, and need a bytecode representation for your opcodes, to store it in the VM's memory. You might consider something like this :

pub enum Register{
    AX,
    BX,
    ...
}

pub enum Opcode{
    Nop,
    Hlt,
    Add(Register,Register),
    AddI(Register,u16),
    ...
}
Enter fullscreen mode Exit fullscreen mode

If you try to write functions to compile and parse these two enums to a simple bytecode, it would be something like this :

impl Register{
    ...
    fn compile(&self)->Vec<u8>{
        match self{
            Register::AX => vec![0],
            Register::BX => vec![1],
            ...
        }
    }
    fn parse(bytes:&[u8])->Result<Self,&str>{
        match bytes[0]{
            1 => Ok(Register::AX),
            2 => Ok(Register::BX),
            ...
            _ => Err("Invalid opcode")
        }
    }
    ...
}

impl Opcode{
    ...
    fn compile(&self)->Vec<u8>{
        match self{
            Opcode::Nop => vec![0],
            Opcode::Hlt => vec![1],
            Opcode::Add(r1,r2) => {
                let mut v = Vec::with_capacity(2);
                v.extend(&r1.compile());
                v.extend(&r2.compile());
                v
            }
            Opcode::AddI(r1,v1) =>{
                let mut v = Vec::with_capacity(3);
                v.extend(&r1.compile());
                v.extend(&v1.to_le_bytes());
                v
            }
            ...
        }
    }
    fn parse(bytes:&[u8])->Result<Self,&str>{
        match bytes[0]{
            1 => Ok(Opcode::Nop),
            2 => Ok(Opcode::Hlt),
            3 =>{
                let r1 = Register::parse(&bytes[1..])?;
                ...
            }
            ...
            _ => Err("Invalid opcode")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Even for the two instructions I have shown, this is pretty long, tedious and boring.

And now consider doing this for at least 25 to upto 100-ish opcodes, as you might need. 😟 😰

And now consider you want to remove a variant or add a variant in the middle of an enum 😰 😱

You will need to shift all the values accordingly, manually.
Not Fun.

The macro will do this for you, and let you concentrate on implementing your VM.
Fun. (hopefully).

Links

The GitHub contains more information and detailed example of usage, so go check it out and ⭐ it !

GitHub logo YJDoc2 / Bytecode

A Rust proc-macro crate which derives functions to compile and parse back enums and structs to and from a bytecode representation

Bytecode

A simple way to derive bytecode for you Enums and Structs.

What is this

This is a crate that provides a proc macro which will derive bytecode representation of your enums and structs, and provides compile and parse functions to convert to and from the bytecode. This also provides necessary traits to do so, in case you want to do it manually.

Note : The values of the fields are compiled as little-endian values, so in the bytecode the smallest byte is at smallest location. The bytecode itself is Big-endian for certain reasons.

Example

Cargo.toml

...
[dependencies]
...
bytecode = {git = "https://github.com/YJDoc2/Bytecode" }
...
Enter fullscreen mode Exit fullscreen mode

Code

use bytecode::{Bytecodable, Bytecode}
#[derive(Bytecode, Debug, PartialEq, Eq)]
pub enum Register {
    AX,
    BX,
    CX,
    DX,
}

#[derive(Bytecode, Debug, PartialEq, Eq)]
pub struct Mem {
    segment: Register,
    offset: Register,
    imOffset: u16,
}

#[derive(Bytecode, Debug, PartialEq, Eq)]
Enter fullscreen mode Exit fullscreen mode

Thank you!

Top comments (0)