Skip to content

Commit

Permalink
Added support for wasmu and compile & run
Browse files Browse the repository at this point in the history
  • Loading branch information
crazystylus committed Jun 22, 2022
1 parent f72fc66 commit 3b87621
Show file tree
Hide file tree
Showing 8 changed files with 1,919 additions and 152 deletions.
1,777 changes: 1,691 additions & 86 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@ categories = ["compiler", "command-line-utilities"]
description = "A brainfuck compiler which compiles to raw wasm"
name = "brainfk-rs"
license = "MIT"
version = "0.1.0"
version = "0.1.1"
repository = "https://github.com/crazystylus/brainfk-rs.git"
edition = "2021"

[dependencies]
clap = { version = "3.2.2", features = ["derive"] }
wasm-encoder = "0.13"
wasmer = { version = "2.3.0", features = ["default", "llvm", "singlepass"] }
wasmer-wasi = "2.3.0"
wasmparser = "0.86.0"

[profile.release]
strip = "symbols"
lto = true
opt-level = "s"
debug = false
codegen-units = 1
53 changes: 27 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
# Brainfk -> WASM
## Introduction
This repo contains a brainfk compiler written in Rust which compiles code directly to wasm. Later the generated wasm file can be ran using wasmer.
### Install Wasmer Runtime
Follow this link to install wasmer-runtime
https://docs.wasmer.io/ecosystem/wasmer/getting-started
This repo contains a brainfk compiler written in Rust closely tied to wasm.

### Example
```shell
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/brainfk-rs`
error: The following required arguments were not provided:
--target <TARGET>
<INPUT_FILE>
<OUTPUT_FILE>
### Features
- Generate wasm from brainfk code
- Compile and run brainfk code
- Compile brainfk to wasmu (wasmer module serial format) which can run with wasmer-headless
- Supports following backends
- LLVM (uses LLVM 12)
- Cranelift
- Singlepass

USAGE:
brainfk-rs --target <TARGET> <INPUT_FILE> <OUTPUT_FILE>
### Usage
```
# JIT Compile & Execute brainfk code
$ brainfk-rs run tests/files/hello.bf --backend cranelift
Hello World!
For more information try --help
$ brainfk-rs generate-wasm tests/files/hello.bf hello.wasm --target wasi
✔ Successfully generated wasm.
$ cargo run -- tests/files/hello.f hello.wasm --target wasi
Compiling brainfk-rs v0.1.0 (/root/Documents/brainfk-rs)
Finished dev [unoptimized + debuginfo] target(s) in 0.91s
Running `target/debug/brainfk-rs tests/files/hello.f abc.wasm --target wasi`
$ brainfk-rs compile-wasmu tests/files/hello.bf hello.wasmu --backend cranelift
✔ Compiled successfully to wasmu.
Compiled file can be executed using wasmer-headless.
$ wasmer run hello.wasm
# Running in wasmer-headless
$ ./wasmer-headless run hello.wasmu
Hello World!
```
### Support
- [X] WASI (It uses STDIN and STDOUT)
- [ ] Browser

### Install Wasmer Runtime
Follow this link to install wasmer-runtime
https://docs.wasmer.io/ecosystem/wasmer/getting-started

### TODO
- [ ] Memory access validation and growth
- [ ] Add support for JS/Browser
- [ ] Integrate with wasmer and support run & compile
- [x] Integrate with wasmer and support run & compile
- [ ] Add testing
- [ ] Add support for JS/Browser
8 changes: 6 additions & 2 deletions src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ pub(crate) fn minus(f: &mut Function) {
/// Output the byte at the data pointer
pub(crate) fn dot(f: &mut Function, target: &crate::Target) {
match target {
Target::Browser => {}
Target::Browser => {
todo!();
}
Target::Wasi => {
// Write IO Vector
f.instruction(&Instruction::I32Const(0));
Expand Down Expand Up @@ -86,7 +88,9 @@ pub(crate) fn dot(f: &mut Function, target: &crate::Target) {
/// Accept one byte of input, storing its value in the byte at the data pointer
pub(crate) fn comma(f: &mut Function, target: &crate::Target) {
match target {
Target::Browser => {}
Target::Browser => {
todo!();
}
Target::Wasi => {
// Write IO Vector
f.instruction(&Instruction::I32Const(0));
Expand Down
90 changes: 79 additions & 11 deletions src/lang.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::compiler;
use crate::Target;
use crate::{Backend, Target};
use std::collections::HashSet;
use std::fs;
use std::io;
Expand All @@ -10,19 +10,41 @@ use wasm_encoder::{
CodeSection, Function, FunctionSection, ImportSection, Instruction, MemorySection, MemoryType,
Module, TypeSection, ValType,
};
use wasmer::Module as WasmerModule;
use wasmer::{Cranelift, Instance, Singlepass, Store, Universal, LLVM};
use wasmer_wasi::{Stdin, Stdout, WasiState};

pub(crate) struct Language {
/// This contains the characterset from brainfk language
pub char_set: HashSet<char>,
/// Character by language symbols
pub code: Vec<char>,
/// Generated bytecode
pub wasm_bytes: Vec<u8>,
}

impl Language {
pub fn new() -> Self {
Self {
char_set: HashSet::from(['<', '>', '+', '-', '.', ',', '[', ']']),
code: Vec::new(),
wasm_bytes: Vec::new(),
}
}
pub fn code_gen(&self, code: &[char], target: &Target) -> Result<Vec<u8>, io::Error> {

/// Open and parse the code
pub fn parse(&mut self, input_file: &str) -> Result<(), io::Error> {
let path = std::path::Path::new(input_file);
let content = fs::read_to_string(path)?;
self.code = content
.chars()
.filter(|x| self.char_set.contains(x))
.collect();
Ok(())
}

/// Generate WASM bytecode
pub fn generate_wasm(&mut self, target: &Target) -> Result<(), io::Error> {
// Create a new module
let mut module = Module::new();
// Type section for void function
Expand All @@ -32,6 +54,7 @@ impl Language {
match target {
Target::Browser => {
// Types for JS functions
todo!();
}
Target::Wasi => {
// Types for fd_read and fd_write
Expand All @@ -50,6 +73,7 @@ impl Language {
match target {
Target::Browser => {
// Import JS functions
todo!();
}
Target::Wasi => {
// Import WASI functions
Expand Down Expand Up @@ -102,16 +126,16 @@ impl Language {
f.instruction(&Instruction::I32Const(1024));
f.instruction(&Instruction::LocalSet(0));

// Code matching
// Symbol matching
let mut fcount: u32 = 2;
for symb in code {
for symb in &self.code {
match symb {
'<' => compiler::less_than(&mut f),
'>' => compiler::greater_than(&mut f),
'+' => compiler::plus(&mut f),
'-' => compiler::minus(&mut f),
',' => compiler::comma(&mut f, &target),
'.' => compiler::dot(&mut f, &target),
',' => compiler::comma(&mut f, target),
'.' => compiler::dot(&mut f, target),
'[' => {
fcount += 1;
compiler::sq_start(&mut f, fcount);
Expand All @@ -128,11 +152,55 @@ impl Language {
codes.function(&f);
module.section(&codes);

let wasm_bytes = module.finish();
Ok(wasm_bytes)
self.wasm_bytes = module.finish();
Ok(())
}

/// Validate generated WASM bytecode
pub fn validate(&self) -> Result<wasmparser::types::Types, wasmparser::BinaryReaderError> {
wasmparser::validate(&self.wasm_bytes)
}

/// Write generated WASM bytecode to file
pub fn write_wasm(&self, output_file: &str) -> Result<(), io::Error> {
fs::write(output_file, &self.wasm_bytes)
}

/// Compile wasm to wasmu
pub fn compile_wasmu(&self, output_file: &str, backend: &Backend) -> Result<(), io::Error> {
let engine = match backend {
&Backend::LLVM => Universal::new(LLVM::default()).engine(),
&Backend::Cranelift => Universal::new(Cranelift::default()).engine(),
&Backend::Singlepass => Universal::new(Singlepass::default()).engine(),
};
let store = Store::new(&engine);
let module = WasmerModule::new(&store, &self.wasm_bytes).unwrap();
module.serialize_to_file(output_file).unwrap();
Ok(())
}
pub fn validate(&self, wasm_bytes: &[u8], output_file: &str) -> Result<(), io::Error> {
wasmparser::validate(wasm_bytes).unwrap();
fs::write(output_file, wasm_bytes)

/// TODO: Compile to a binary using wasmer
pub fn compile_binary(&self, _output_file: &str, _backend: &Backend) -> Result<(), io::Error> {
todo!();
}

/// Run bf-code
pub fn run(&self, backend: &Backend) {
let engine = match backend {
&Backend::LLVM => Universal::new(LLVM::default()).engine(),
&Backend::Cranelift => Universal::new(Cranelift::default()).engine(),
&Backend::Singlepass => Universal::new(Singlepass::default()).engine(),
};
let store = Store::new(&engine);
let module = WasmerModule::new(&store, &self.wasm_bytes).unwrap();
let mut wasi_env = WasiState::new("brainfk")
.stdin(Box::new(Stdin))
.stdout(Box::new(Stdout))
.finalize()
.unwrap();
let import_object = wasi_env.import_object(&module).unwrap();
let instance = Instance::new(&module, &import_object).unwrap();
let start = instance.exports.get_function("_start").unwrap();
start.call(&[]).unwrap();
}
}
Loading

0 comments on commit 3b87621

Please sign in to comment.