Skip to content

Commit 0cd2b0e

Browse files
committed
Complete risc-v implementation
This took so long after several attempts to try and get loading a 64 bit immediate to work - I just couldn't manage to do it. So I'll leave that for another time. It doesn't help that there's no documentation on doing so because it's generally frowned upon. It's unfortunate as dasm is in a special place where it'd be very convenient to do so to use as a JIT.
1 parent 420604a commit 0cd2b0e

File tree

14 files changed

+352
-341
lines changed

14 files changed

+352
-341
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "dasm"
33
authors = ["David Cruz <[email protected]>"]
44
description = "A tiny, zero dependency assembler"
5-
version = "0.1.2"
5+
version = "0.2.0"
66
edition = "2021"
77
keywords = ["assembler", "jit", "dasm", "x86", "codegen"]
88
categories = ["os", "no-std", "no-std::no-alloc"]

README.md

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,62 +18,46 @@
1818

1919
## Note
2020

21-
Support for every single instruction is **NOT** planned.
22-
Nor will there be any passes for optimization.
21+
`dasm` has a goal of acting as a lightweight, simple instructions for compilers.
2322

24-
This has a simple goal of assembling instructions for compilers.
25-
If you want a fully featured library, please refer to [`Iced`](https://github.com/icedland/iced)
23+
If you want a higher-level library or something more comprehensive, there's other great solutions like [`Iced`](https://github.com/icedland/iced) or [`dynasm-rs`](https://github.com/CensoredUsername/dynasm-rs).
2624

2725
## How it works
2826

2927
Code generation doesn't have to be hard. This just provides explicit functions for generating instructions.
30-
No abstractions for the sake of safety or optimization which add complexity. If you just want to write assembly, this is for you.
31-
32-
**This library has tiered abstraction levels.**
33-
34-
### Raw
35-
36-
This gives you access to the "raw" bytes from an instruction given untagged numeric arguments.
37-
Despite how it sounds, it is pretty nice to use on its own.
28+
No abstractions through macros, or some DSL you need to learn. Just functions.
3829

3930
**Example**
4031

4132
```rust
33+
use dasm::tier::raw::amd64::*;
34+
4235
let rax = 0;
4336
let rsi = 6; // Argument 2
4437
let rdi = 7; // Argument 1
4538

4639
let asm = [
47-
&dasm::tier::raw::amd64::mov_r64_r64(rax, rdi) as &[u8],
48-
&dasm::tier::raw::amd64::add_r64_r64(rax, rsi),
49-
&dasm::tier::raw::amd64::ret()
40+
&mov_r64_r64(rax, rdi) as &[u8],
41+
&add_r64_r64(rax, rsi),
42+
&ret()
5043
].concat();
5144

52-
// A helper for making memory executable is included.
45+
// Allocate an executable memory block
5346
let mmapped = dasm::mmap::Mmap::exec(&asm)
5447
.expect("Failed to mmap");
5548

56-
// Simply cast the bytes to the function you just made.
49+
// Simply cast that memory into a function to call it.
5750
let adder: extern "C" fn(x: u64, y: u64) -> u64 = unsafe { std::mem::transmute(mmapped.as_ptr()) };
5851
assert_eq!(adder(5, 200), 205);
5952
```
6053

61-
There's also an example showcasing a tiny AOT compiled lisp at [`examples/tinylisp`](https://github.com/DvvCz/dasm/tree/master/examples/tinylisp).
62-
63-
## Other Tiers
54+
*There's also an example showcasing a tiny AOT compiled lisp at [`examples/tinylisp`](https://github.com/DvvCz/dasm/tree/master/examples/tinylisp).*
6455

65-
At the moment, other tiers are not implemented as I plan out how these abstractions would go.
66-
67-
Hopefully they'd involve abstracting away overloads with tagged enums and cross architecture compatibility.
6856

6957
## Why
7058

71-
I felt the need for an assembler.
72-
73-
LLVM was out of the question due to its size and difficult integration to Rust.
74-
75-
I struggled to work with Cranelift due to its complexity for the sake of safety and performance.
76-
77-
But sometimes, you don't care about safety or performance. You just want a simple solution to get it done.
59+
All of the other solutions are either too big for my usecase (`LLVM`), too complex (`Cranelift`) or have too many abstractions.
60+
Sometimes, you don't need all of that - you just need to write some assembly.
61+
That's what `dasm` is for.
7862

79-
That's what `dasm` is supposed to be.
63+
*Of course - No shade to any other library. Anyone can use the tool they prefer.*

examples/hello/src/main.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use dasm::tier::raw::amd64::*;
2+
13
fn main() {
24
let mut mem: Vec<u8> = vec![];
35

@@ -9,17 +11,14 @@ fn main() {
911
let rsi = 6; // Argument #2 on linux
1012

1113
// Calls linux sys_write with given string and length
12-
mem.extend(dasm::tier::raw::amd64::mov_r64_i64(rdi, 1)); // fd
13-
mem.extend(dasm::tier::raw::amd64::mov_r64_i64(rax, 1)); // sys_write
14-
mem.extend(dasm::tier::raw::amd64::mov_r64_i64(rsi, message.as_ptr() as _));
15-
mem.extend(dasm::tier::raw::amd64::mov_r64_i64(rdx, message.len() as _));
16-
mem.extend(dasm::tier::raw::amd64::syscall());
17-
18-
// Still need to return
19-
mem.extend(dasm::tier::raw::amd64::ret());
14+
mem.extend(mov_r64_i64(rdi, 1)); // fd
15+
mem.extend(mov_r64_i64(rax, 1)); // sys_write
16+
mem.extend(mov_r64_i64(rsi, message.as_ptr() as _));
17+
mem.extend(mov_r64_i64(rdx, message.len() as _));
18+
mem.extend(syscall());
19+
mem.extend(ret());
2020

21-
let map = dasm::mmap::Mmap::exec(&mem)
22-
.expect("Failed to mmap");
21+
let map = dasm::mmap::Mmap::exec(&mem).expect("Failed to mmap");
2322

2423
let f: extern "C" fn() = unsafe { std::mem::transmute(map.as_ptr()) };
2524
f();

examples/rv32/README.md

Lines changed: 0 additions & 7 deletions
This file was deleted.

examples/rv32/src/main.rs

Lines changed: 0 additions & 73 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
name = "rv32"
2+
name = "rv64"
33
version = "0.1.0"
44
authors = ["David Cruz <[email protected]>"]
55
edition = "2021"

examples/rv64/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# rv64
2+
3+
Example for generating RISC-V (rv64gc) instructions.
4+
5+
```
6+
cargo run --package rv64
7+
```

examples/rv64/src/main.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
fn main() -> Result<(), Box<dyn std::error::Error>> {
2+
use dasm::tier::raw::rv64::*;
3+
4+
let a0 = 10;
5+
let a1 = 11;
6+
let a2 = 12;
7+
let a7 = 17;
8+
9+
// Calls linux sys_write with given string and length
10+
// Note that this implementation stores the string in the same memory, since
11+
// there's no good way to load a 64 bit immediate address in risc-v.
12+
let mut mem: Vec<u8> = vec![];
13+
// .text
14+
mem.extend(addi(a0, x0, 1).to_le_bytes()); // stdout
15+
mem.extend(auipc(a1, 0).to_le_bytes()); // a1 = pc
16+
mem.extend(addi(a1, a1, 24).to_le_bytes()); // string = pc + 24
17+
mem.extend(addi(a2, x0, 14).to_le_bytes()); // length of string
18+
mem.extend(addi(a7, x0, 64).to_le_bytes()); // sys_write
19+
mem.extend(ecall().to_le_bytes());
20+
mem.extend(ret().to_le_bytes());
21+
// .data
22+
mem.extend(b"Hello, world!\n");
23+
24+
let map = dasm::mmap::Mmap::exec(&mem)?;
25+
let f: extern "C" fn() = unsafe { std::mem::transmute(map.as_ptr()) };
26+
f();
27+
28+
Ok(())
29+
}

src/tier/raw/amd64/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
//! `amd64` implementation
2+
//!
3+
//! For 64 bit x86. This includes all compatible instructions from x86 (alongside 64 bit equivalents.)
4+
5+
16
// amd64 should support all of these.
27
pub use crate::tier::raw::x86::compatible::*;
38

0 commit comments

Comments
 (0)