Skip to content

Commit f309dfa

Browse files
author
Arnaud Riess
committed
fix: update README.md for clarity and improved project status description
1 parent f61cc9f commit f309dfa

1 file changed

Lines changed: 133 additions & 29 deletions

File tree

README.md

Lines changed: 133 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,160 @@
11
# herkos
22

3-
> ⚠️ **This project is work in progress! Not all wasm features nor corner cases were tested! Do not use in production!**
3+
[![CI](https://github.com/arnoox/herkos/actions/workflows/ci.yml/badge.svg)](https://github.com/arnoox/herkos/actions/workflows/ci.yml)
4+
[![Crates.io](https://img.shields.io/crates/v/herkos.svg)](https://crates.io/crates/herkos)
5+
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
6+
[![no_std](https://img.shields.io/badge/no__std-compatible-green.svg)]()
47

5-
A compilation pipeline that transpiles WebAssembly modules into memory-safe Rust code with compile-time isolation guarantees (memory+capabilities), replacing runtime hardware-based memory protection (MMU/MPU) with type-system-enforced safety.
8+
**Compile-time memory isolation via WebAssembly-to-Rust transpilation.**
69

7-
herkos approach: if the Rust compiler accepts the transpiled code, isolation is guaranteed; no MMU, no context switches, no runtime overhead for proven accesses.
10+
herkos transpiles WebAssembly modules into safe Rust code, offering type-system-enforced isolation as a lightweight alternative to hardware-based memory protection (MMU/MPU). When hardware isolation is overkill or simply unavailable, herkos lets the Rust compiler enforce the guarantees instead: no MMU, no context switches, no runtime overhead for proven accesses.
811

9-
**WebAssembly → Rust source → Safe binary**
12+
> **Status:** Work in progress. Not all Wasm features or corner cases are covered. Do not use in production.
1013
11-
## Motivation
14+
## The idea
1215

13-
Running untrusted or unsafe-language components alongside safe code usually requires hardware isolation (MMU/MPU, hypervisors) or process boundaries, all of which add runtime overhead and complexity. What if the compiler itself could enforce "spatial" isolation?
16+
Running untrusted or unsafe-language code usually requires hardware isolation (processes, hypervisors, MMU/MPU). That's the right tool for many jobs — but sometimes it's overkill: a small plugin, a single C library, a microcontroller with no MMU. For those cases, herkos offers a lighter-weight approach: transpile Wasm to safe Rust, so the compiler itself enforces spatial isolation.
1417

15-
herkos explores this idea: transpile WebAssembly modules into safe Rust, so that memory isolation and capability restrictions are checked at compile time rather than at runtime. This opens up several use cases:
18+
```
19+
Compile-time isolation
20+
┌──────────────────────┐
21+
C / C++ / Rust ──────▶ │ .wasm ──herkos──▶│ Safe Rust ──rustc──▶ Binary
22+
(unsafe code) clang/ │ │ (no unsafe) (isolated)
23+
rustc └──────────────────────┘
24+
```
1625

17-
- **Isolating untrusted components** — sandbox C/C++ libraries without hardware protection
18-
- **Porting unsafe-language code to Rust** — use Wasm as an intermediate representation to get a safe Rust version of existing C/C++ code
19-
- **Efficient cross-partition communication** — how do "compile-time-MMU" partitions talk to each other efficiently?
26+
This enables:
27+
- **Sandboxing C/C++ libraries** without hardware protection: isolate a JPEG decoder, a crypto library, or a plugin system
28+
- **Porting unsafe code to Rust**: use Wasm as an IR to get a safe Rust version of existing C/C++ code
29+
- **Bare-metal isolation**: `#![no_std]`, no heap, no OS required. Run isolated components on microcontrollers
30+
31+
## Example: C to safe Rust in 3 steps
32+
33+
**1. Write C code** (or any language that compiles to Wasm):
34+
```c
35+
int fibonacci(int n) {
36+
if (n <= 0) return 0;
37+
if (n == 1) return 1;
38+
int a = 0, b = 1, i = 2;
39+
while (i <= n) { int tmp = a + b; a = b; b = tmp; i++; }
40+
return b;
41+
}
42+
```
2043
21-
## Architecture
44+
**2. Compile to Wasm, then transpile:**
45+
```bash
46+
clang --target=wasm32 -O2 -nostdlib -Wl,--no-entry -Wl,--export-all -o fibonacci.wasm fibonacci.c
47+
herkos fibonacci.wasm --output fibonacci.rs
48+
```
2249

23-
The project is a Rust workspace with three core crates:
50+
**3. Use from Rust — every call returns `Result`, traps become errors:**
51+
```rust
52+
mod fibonacci;
2453

25-
| Crate | Purpose |
26-
|---|---|
27-
| `herkos` | CLI transpiler: parses `.wasm` binaries, emits Rust source code |
28-
| `herkos-runtime` | `#![no_std]` runtime library shipped with transpiled output (isolated memory, capability types, wasm operations) |
29-
| `herkos-tests` | collection of wat/Rust/C sources that are compiled to .wasm, transpiled and tested. |
54+
fn main() {
55+
let mut module = fibonacci::new().expect("init failed");
56+
let result = module.fibonacci(10).unwrap();
57+
assert_eq!(result, 55);
58+
}
59+
```
3060

31-
## Build and test
61+
The generated `fibonacci.rs` contains **no `unsafe`**, **no function pointers**, **no heap allocations**. Memory access is bounds-checked, division by zero returns `Err(WasmTrap::DivisionByZero)`, and the module's memory is structurally isolated from the rest of your program.
62+
63+
See the full [C-to-Wasm-to-Rust example](examples/c-to-wasm-to-rust/).
64+
65+
## How it works
66+
67+
```
68+
.wasm ─▶ Parser ─▶ IR Builder ─▶ Optimizer ─▶ Backend ─▶ Codegen ─▶ rustfmt ─▶ output.rs
69+
wasmparser SSA-form dead block safe Rust
70+
IR elimination backend source
71+
```
72+
73+
The transpiler parses a `.wasm` binary, builds an SSA-form intermediate representation, optimizes it, and emits Rust source code through a safe backend. The output depends only on the `herkos-runtime` crate.
74+
75+
### Generated code structure
76+
77+
Each Wasm module becomes a Rust struct that owns its isolated memory:
78+
79+
```rust
80+
pub struct WasmModule(pub Module<Globals, MAX_PAGES, TABLE_SIZE>);
81+
82+
impl WasmModule {
83+
pub fn fibonacci(&mut self, n: i32) -> WasmResult<i32> { ... }
84+
pub fn add(&mut self, a: i32, b: i32) -> WasmResult<i32> { ... }
85+
}
86+
```
87+
88+
- **Memory**`IsolatedMemory<MAX_PAGES>`: a fixed-size 2D array, fully stack/BSS allocated, no heap. Every load/store is bounds-checked
89+
- **Imports** — become trait bounds: `fn send<H: SocketOps>(host: &mut H, ...)`. No imports = no host parameter
90+
- **Exports** — become methods on the module struct
91+
- **Indirect calls** — safe `match` dispatch over function indices. No function pointers, no vtables
92+
- **Errors**`WasmResult<T> = Result<T, WasmTrap>`. No panics, no unwinding
93+
94+
### Security properties
95+
96+
| Property | How |
97+
|----------|-----|
98+
| No buffer overflows | Every memory access is bounds-checked against `active_pages * PAGE_SIZE` |
99+
| No cross-module access | Each module owns a distinct `IsolatedMemory`; the type system prevents cross-access |
100+
| No unauthorized syscalls | Imports are trait bounds — you can't call `socket_open` unless the host implements `SocketOps` |
101+
| No ROP gadgets | No function pointers in generated code; indirect calls use static match dispatch |
102+
| No panics | All errors are `Result<T, WasmTrap>`; no unwinding |
103+
104+
## Quick start
105+
106+
### Install
32107

33108
```bash
34-
cargo build # build all crates
35-
cargo test # run all tests
36-
cargo clippy --all-targets # lint
37-
cargo fmt --check # format check
109+
cargo install herkos
38110
```
39111

40-
Run a single crate's tests:
112+
Or from source:
41113

42114
```bash
43-
cargo test -p herkos
44-
cargo test -p herkos-runtime
45-
cargo test -p herkos-tests
115+
git clone https://github.com/arnoox/herkos.git
116+
cd herkos
117+
cargo install --path crates/herkos
46118
```
47119

48-
## Usage
120+
### Transpile
49121

50122
```bash
51-
cargo run -p herkos -- input.wasm --output output.rs
123+
herkos input.wasm --output output.rs
52124
```
53125

126+
### Use from `build.rs` (compile-time pipeline)
127+
128+
```rust
129+
let wasm_bytes = std::fs::read("module.wasm").unwrap();
130+
let rust_code = herkos::transpile(&wasm_bytes, &herkos::Options::default()).unwrap();
131+
std::fs::write(out_dir.join("module.rs"), rust_code).unwrap();
132+
```
133+
134+
## Project structure
135+
136+
| Crate | Purpose | `no_std` |
137+
|-------|---------|----------|
138+
| [`herkos`](crates/herkos/) | CLI transpiler: `.wasm` binary in, Rust source out | No |
139+
| [`herkos-runtime`](crates/herkos-runtime/) | Runtime library shipped with transpiled output | Yes |
140+
| [`herkos-tests`](crates/herkos-tests/) | Integration tests + benchmarks | No |
141+
142+
## Build and test
143+
144+
```bash
145+
cargo build # build all crates
146+
cargo test # run all tests
147+
cargo clippy --all-targets # lint
148+
cargo fmt --check # format check
149+
cargo bench -p herkos-tests # benchmarks
150+
```
151+
152+
## Documentation
153+
154+
- [Requirements](docs/REQUIREMENTS.md) — formal requirements (REQ_* IDs)
155+
- [Specification](docs/SPECIFICATION.md) — architecture, transpilation rules, memory model, security analysis
156+
- [Future work](docs/FUTURE.md) — verified backend, hybrid backend, temporal isolation
157+
54158
## License
55159

56-
Licensed under the Apache License, Version 2.0 ([LICENSE](LICENSE))
160+
Licensed under the Apache License, Version 2.0 ([LICENSE](LICENSE)).

0 commit comments

Comments
 (0)