Skip to content

Commit

Permalink
Updated project
Browse files Browse the repository at this point in the history
  • Loading branch information
BrendoCosta committed Sep 2, 2024
1 parent b7229e0 commit 4881766
Show file tree
Hide file tree
Showing 41 changed files with 2,638 additions and 736 deletions.
82 changes: 81 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,89 @@

An experimental work-in-progress (WIP) WebAssembly runtime written in Gleam.

## Purpose

Nowadays, many languages ​​support Wasm as a target, from mainstream ones like C++ and Rust, as well as newer ones like Odin and Grain. The purpose of this project is to use WebAssembly to create an alternative interoperability layer to Erlang's virtual machine NIFs.

## Installation

```sh
gleam add gwr
```

## Usage

TODO.
> [!IMPORTANT]
> Currently the project is in an extremely early stage of development, it is only possible to run a simple sum function. Keep in mind that code and APIs may change dramatically.
### Step 1 - Build code targeting Wasm

#### Example - from Rust

```rust
// sum.rs

#![no_std]

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> !
{
loop {}
}

#[no_mangle]
pub extern fn sum(x: i32, y: i32) -> i32
{
x + y
}
```
```sh
rustc --crate-type cdylib --target wasm32-unknown-unknown -C debuginfo=none -C panic=abort -C strip=symbols -C opt-level=3 ./sum.rs -o ./sum.wasm
```

#### Example - from Wat

Using the wat2wasm tool from [wabt](https://github.com/WebAssembly/wabt).

```wasm
;; sum.wat
(module
(type $t0 (func (param i32 i32) (result i32)))
(func $sum (export "sum") (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
(i32.add (local.get $p0) (local.get $p1))
)
)
```
```sh
wat2wasm -o ./sum.wasm ./sum.wat
```

### Step 2 - Run it from Gleam with GWR

Using the [simplifile](https://hex.pm/packages/simplifile) package to read the module file.

```sh
gleam add simplifile
```

```gleam
import gwr/execution/instance
import gwr/execution/runtime
import simplifile
pub fn main()
{
let assert Ok(module_data) = simplifile.read_bits(from: "sum.wasm")
let assert Ok(instance) = instance.create(from: module_data)
let assert Ok(#(instance, result)) = instance.call(instance, "sum", [runtime.Number(4), runtime.Number(2)])
let assert [runtime.Number(6)] = result
}
```

## Contributing

Contributions are welcome! Feel free to submit either issues or PRs, but keep in mind that your code needs to be covered by tests.

## License

Expand Down
3 changes: 2 additions & 1 deletion gleam.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = "gwr"
version = "0.0.1"
version = "0.0.2"
description = "An experimental work-in-progress (WIP) WebAssembly runtime written in Gleam."
licences = ["MIT"]
repository = { type = "github", user = "BrendoCosta", repo = "gwr" }
Expand All @@ -10,3 +10,4 @@ gleb128 = ">= 2.0.0 and < 3.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
simplifile = ">= 2.1.0 and < 3.0.0"
3 changes: 3 additions & 0 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
# You typically do not need to edit this file

packages = [
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
{ name = "gleam_stdlib", version = "0.39.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "2D7DE885A6EA7F1D5015D1698920C9BAF7241102836CE0C3837A4F160128A9C4" },
{ name = "gleb128", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleb128", source = "hex", outer_checksum = "6FBBA4F89A0B515EBC951AAFCD824F54FAD03A81A55C11F4CC9EEC87DE0A2040" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
{ name = "simplifile", version = "2.1.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "BDD04F5D31D6D34E2EDFAEF0B68A6297AEC939888C3BFCE61133DE13857F6DA2" },
]

[requirements]
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleb128 = { version = ">= 2.0.0 and < 3.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
simplifile = { version = ">= 2.1.0 and < 3.0.0"}
126 changes: 126 additions & 0 deletions src/gwr/binary.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import gleam/option.{type Option}

import gwr/syntax/convention
import gwr/syntax/instruction
import gwr/syntax/types
import gwr/syntax/module

/// https://webassembly.github.io/spec/core/binary/module.html#sections
pub const custom_section_id = 0x00
pub const type_section_id = 0x01
pub const import_section_id = 0x02
pub const function_section_id = 0x03
pub const table_section_id = 0x04
pub const memory_section_id = 0x05
pub const global_section_id = 0x06
pub const export_section_id = 0x07
pub const start_section_id = 0x08
pub const element_section_id = 0x09
pub const code_section_id = 0x0a
pub const data_section_id = 0x0b
pub const data_count_section_id = 0x0c

pub type Binary
{
Binary
(
version: Int,
length: Int,
module: module.Module
)
}

/// Each section consists of
/// - a one-byte section id,
/// - the u32 size of the contents, in bytes,
/// - the actual contents, whose structure is dependent on the section id.
/// https://webassembly.github.io/spec/core/binary/module.html#sections
pub type Section
{
Section(id: Int, length: Int, content: Option(SectionContent))
}

pub type SectionContent
{
/// Custom sections have the id 0. They are intended to be used for debugging information
/// or third-party extensions, and are ignored by the WebAssembly semantics. Their contents
/// consist of a name further identifying the custom section, followed by an uninterpreted
/// sequence of bytes for custom use.
///
/// https://webassembly.github.io/spec/core/binary/module.html#custom-section
CustomSection(name: String, data: Option(BitArray))

/// The type section has the id 1. It decodes into a vector of function types that represent
/// the component of a module.
///
/// https://webassembly.github.io/spec/core/binary/module.html#type-section
TypeSection(function_types: convention.Vector(types.FunctionType))

// @TODO
ImportSection

/// The function section has the id 3. It decodes into a vector of type indices that represent
/// the <type> fields of the functions in the <funcs> component of a module. The <locals> and
/// <body> fields of the respective functions are encoded separately in the code section.
///
/// https://webassembly.github.io/spec/core/binary/module.html#function-section
FunctionSection(type_indices: convention.Vector(Int))

/// The table section has the id 4. It decodes into a vector of tables that represent the <table>
/// component of a module.
///
/// https://webassembly.github.io/spec/core/binary/modules.html#table-section
// @TODO
TableSection

/// The memory section has the id 5. It decodes into a vector of memories that represent the <mems>
/// component of a module.
///
/// https://webassembly.github.io/spec/core/binary/modules.html#memory-section
MemorySection(memories: convention.Vector(module.Memory))

/// The global section has the id 6. It decodes into a vector of globals that represent the <globals>
/// component of a module.
///
/// https://webassembly.github.io/spec/core/binary/modules.html#global-section
GlobalSection(globals: convention.Vector(module.Global))

/// The export section has the id 7. It decodes into a vector of exports that represent the exports
/// component of a module.
///
/// https://webassembly.github.io/spec/core/binary/module.html#export-section
ExportSection(exports: convention.Vector(module.Export))

/// The start section has the id 8. It decodes into an optional start function that represents the
/// <start> component of a module.
///
/// https://webassembly.github.io/spec/core/binary/modules.html#start-section
StartSection(start_function: module.StartFunction)

// @TODO
ElementSection

CodeSection(entries: List(Code))

// @TODO
DataSection

// @TODO
DataCountSection
}

// https://webassembly.github.io/spec/core/binary/module.html#code-section
pub type Code
{
Code(size: Int, function_code: FunctionCode)
}

pub type FunctionCode
{
FunctionCode(locals: convention.Vector(LocalsDeclaration), body: instruction.Expression)
}

pub type LocalsDeclaration
{
LocalsDeclaration(count: Int, type_: types.ValueType)
}
45 changes: 45 additions & 0 deletions src/gwr/execution/instance.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import gleam/list
import gleam/result

import gwr/binary
import gwr/execution/machine
import gwr/execution/runtime
import gwr/parser/binary_parser
import gwr/parser/binary_reader
import gwr/syntax/module

pub type WebAssemblyInstance
{
WebAssemblyInstance
(
binary: binary.Binary,
machine: machine.Machine
)
}

pub fn create(from data: BitArray) -> Result(WebAssemblyInstance, String)
{
use #(_, binary) <- result.try(binary_parser.parse_binary_module(binary_reader.create(from: data)))
use machine <- result.try(machine.initialize(binary.module))
Ok(WebAssemblyInstance(binary: binary, machine: machine))
}

pub fn call(instance: WebAssemblyInstance, name: String, arguments: List(runtime.Value)) -> Result(#(WebAssemblyInstance, List(runtime.Value)), String)
{
use function_index <- result.try(
case list.find(in: instance.binary.module.exports, one_that: fn (export) {
case export.name == name, export.descriptor
{
True, module.FunctionExport(_) -> True
_, _ -> False
}
})
{
Ok(module.Export(name: _, descriptor: module.FunctionExport(index))) -> Ok(index)
_ -> Error("gwr/execution/instance.call: couldn't find an exported function with name \"" <> name <>"\" in the given module")
}
)

use #(new_state, results) <- result.try(machine.call(instance.machine.state, function_index, arguments))
Ok(#(WebAssemblyInstance(..instance, machine: machine.Machine(..instance.machine, state: new_state)), results))
}
2 changes: 2 additions & 0 deletions src/gwr/execution/logic.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
//pub fn i32_add(configuration: Configuration, stack: Stack) -> Result(#(Configuration, Stack), String)
//pub fn local_get(configuration: Configuration, stack: Stack) -> Result(#(Configuration, Stack), String)
Loading

0 comments on commit 4881766

Please sign in to comment.