Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
BrendoCosta committed Aug 12, 2024
0 parents commit ac050e5
Show file tree
Hide file tree
Showing 17 changed files with 832 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: test

on:
push:
branches:
- master
- main
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: "26.0.2"
gleam-version: "1.3.2"
rebar3-version: "3"
# elixir-version: "1.15.4"
- run: gleam deps download
- run: gleam test
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.beam
*.ez
/build
erl_crash.dump
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Brendo Costa

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# GWR - Gleam WebAssembly Runtime

[![Package Version](https://img.shields.io/hexpm/v/gwr)](https://hex.pm/packages/gwr)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gwr/)
[![Package License](https://img.shields.io/hexpm/l/gwr)](https://hex.pm/packages/gwr)
[![Package Total Downloads Count](https://img.shields.io/hexpm/dt/gwr)](https://hex.pm/packages/gwr)
[![Build Status](https://img.shields.io/github/actions/workflow/status/BrendoCosta/gwr/test.yml)](https://hex.pm/packages/gwr)
[![Total Stars Count](https://img.shields.io/github/stars/BrendoCosta/gwr)](https://hex.pm/packages/gwr)

## Description

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

## Usage

TODO.

## License

GWR source code is avaliable under the [MIT license](/LICENSE).
12 changes: 12 additions & 0 deletions gleam.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name = "gwr"
version = "0.0.1"
description = "An experimental work-in-progress (WIP) WebAssembly runtime written in Gleam."
licences = ["MIT"]
repository = { type = "github", user = "BrendoCosta", repo = "gwr" }

[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
gleb128 = ">= 2.0.0 and < 3.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
13 changes: 13 additions & 0 deletions manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# This file was generated by Gleam
# You typically do not need to edit this file

packages = [
{ 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" },
]

[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" }
47 changes: 47 additions & 0 deletions src/module.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import gleam/bit_array
import gleam/result

import gleb128

pub type Module
{
Module
(
version: Int
)
}

pub fn try_detect_signature(raw_data: BitArray) -> Bool
{
case bit_array.slice(at: 0, from: raw_data, take: 4)
{
Ok(<<0x00, 0x61, 0x73, 0x6d>>) -> True
_ -> False
}
}

pub fn get_module_version(raw_data: BitArray) -> Result(Int, String)
{
case bit_array.slice(at: 4, from: raw_data, take: 4)
{
Ok(version_raw_data) ->
{
use version <- result.try(gleb128.decode_unsigned(version_raw_data))
Ok(version.0)
}
_ -> Error("module::get_module_version: can't get module version raw data")
}
}

pub fn from_raw_data(raw_data: BitArray) -> Result(Module, String)
{
case try_detect_signature(raw_data)
{
True ->
{
use version <- result.try(get_module_version(raw_data))
Ok(Module(version: version))
}
False -> Error("Can't detect module signature")
}
}
162 changes: 162 additions & 0 deletions src/section.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import gleam/bit_array
import gleam/bool
import gleam/int
import gleam/option.{type Option, Some, None}
import gleam/result

import util
import types/limits.{type Limits}
import types/name
import types/vector

// 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/modules.html#sections
pub type RawSection
{
RawSection(type_id: Int, length: Int, content: Option(BitArray))
}

pub type Section
{
// 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.
Custom(length: Int, name: String, data: Option(BitArray))
Type(length: Int)
Import(length: Int)
Function(length: Int)
Table(length: Int)
Memory(length: Int, memories: List(Limits))
Global(length: Int)
Export(length: Int)
Start(length: Int)
Element(length: Int)
Code(length: Int)
Data(length: Int)
DataCount(length: Int)
}

// https://webassembly.github.io/spec/core/binary/modules.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 fn parse_raw_section(at position: Int, from raw_data: BitArray) -> Result(RawSection, String)
{
use section_type_id <- result.try(
case bit_array.slice(at: position, from: raw_data, take: 1)
{
Ok(section_type_id_raw_data) -> case section_type_id_raw_data
{
<<section_type_id_decoded>> -> Ok(section_type_id_decoded)
_ -> Error("section::parse_raw_section: can't decode section type id raw data into an integer")
}
Error(_) -> Error("section::parse_raw_section: can't get section type id raw data")
}
)

use #(section_length, section_length_word_size) <- result.try(util.decode_u32leb128(at: position + 1, from: raw_data))

let section_raw_content = case section_length > 0
{
True ->
{
use <- bool.guard(
when: position + 1 + section_length_word_size + section_length > bit_array.byte_size(raw_data),
return: Error("section::parse_raw_section: unexpected end of the section's content segment")
)
case bit_array.slice(at: position + 1 + section_length_word_size, from: raw_data, take: section_length)
{
Ok(content) -> Ok(Some(content))
Error(_) -> Error("section::parse_raw_section: can't get the section's content segment")
}
}
False -> Ok(None)
}

use section_raw_content <- result.try(
case section_raw_content
{
Ok(v) -> Ok(v)
Error(reason) -> Error(reason)
}
)

Ok(RawSection(type_id: section_type_id, length: section_length, content: section_raw_content))
}

pub fn decode_section(at position: Int, from raw_data: BitArray) -> Result(Section, String)
{
use parsed_raw_section <- result.try(parse_raw_section(at: position, from: raw_data))
case parsed_raw_section.type_id
{
n if n == custom_section_id -> decode_custom_section(parsed_raw_section)
//n if n == type_section_id -> Ok(Type)
//n if n == import_section_id -> Ok(Import)
//n if n == function_section_id -> Ok(Function)
//n if n == table_section_id -> Ok(Table)
n if n == memory_section_id -> decode_memory_section(parsed_raw_section)
//n if n == global_section_id -> Ok(Global)
//n if n == export_section_id -> Ok(Export)
//n if n == start_section_id -> Ok(Start)
//n if n == element_section_id -> Ok(Element)
//n if n == code_section_id -> Ok(Code)
//n if n == data_section_id -> Ok(Data)
//n if n == data_count_section_id -> Ok(DataCount)
_ -> Error("section::decode_section: unknown section type id \"" <> int.to_string(parsed_raw_section.type_id) <> "\"")
}
}

pub fn decode_custom_section(raw_section: RawSection) -> Result(Section, String)
{
case raw_section.content
{
Some(content) ->
{
use custom_section_name <- result.try(name.from_raw_data(at: 0, from: content))
use name_string <- result.try(name.to_string(custom_section_name))
let name_length = name.length(custom_section_name)
let content_length = bit_array.byte_size(content)

case bit_array.slice(at: name_length + 1, from: content, take: content_length - name_length - 1)
{
Ok(custom_data) -> Ok(Custom(length: raw_section.length, name: name_string, data: Some(custom_data)))
Error(_) -> Error("section::decode_custom_section: can't get custom data")
}
}
None -> Error("section::decode_custom_section: empty section content")
}
}

pub fn decode_memory_section(raw_section: RawSection) -> Result(Section, String)
{
case raw_section.content
{
Some(content) ->
{
use raw_vec <- result.try(vector.from_raw_data(at: 0, from: content))
use mem_vec <- result.try(limits.from_vector(raw_vec))
Ok(Memory(length: raw_section.length, memories: mem_vec.0))
}
None -> Error("section::decode_memory_section: empty section content")
}
}

pub fn from_raw_data(at position: Int, from raw_data: BitArray) -> Result(Section, String)
{
use sec <- result.try(decode_section(at: position, from: raw_data))
Ok(sec)
}
81 changes: 81 additions & 0 deletions src/types/limits.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import gleam/bit_array
import gleam/list
import gleam/option.{type Option, Some, None}
import gleam/result

import util
import types/vector as vector

// https://webassembly.github.io/spec/core/binary/types.html#limits
pub type Limits
{
Limits(min: Int, max: Option(Int))
}

pub fn from_raw_data(at position: Int, from raw_data: BitArray) -> Result(#(Limits, Int), String)
{
// From the spec: "limits are encoded with a preceding flag indicating whether a maximum is present."
use has_max <- result.try(
case bit_array.slice(at: position, from: raw_data, take: 1)
{
Ok(<<0x00>>) -> Ok(False)
Ok(<<0x01>>) -> Ok(True)
Ok(_) | Error(_) -> Error("limits::from_raw_data: can't get has_max flag")
}
)

use #(min, min_word_size) <- result.try(util.decode_u32leb128(at: position + 1, from: raw_data))
let bytes_read = 1 + min_word_size

use maybe_max <- result.try(
case has_max
{
True ->
{
use mx <- result.try(util.decode_u32leb128(at: position + min_word_size + 1, from: raw_data))
Ok(Some(mx))
}
False ->
{
Ok(None)
}
}
)

let bytes_read = case maybe_max
{
Some(#(_, max_word_size)) -> bytes_read + max_word_size
None -> bytes_read
}

case maybe_max
{
Some(#(max, _)) -> Ok(#(Limits(min: min, max: Some(max)), bytes_read))
None -> Ok(#(Limits(min: min, max: None), bytes_read))
}
}

pub fn do_from_vector(at position_accumulator: Int, from vec: vector.Vector, to result_accumulator: List(Limits)) -> Result(#(List(Limits), Int), String)
{
case position_accumulator < vec.length
{
True ->
{
case from_raw_data(at: position_accumulator, from: vec.data)
{
Ok(limit) ->
{
let result_accumulator = list.append(result_accumulator, [limit.0])
do_from_vector(at: position_accumulator + limit.1, from: vec, to: result_accumulator)
}
Error(reason) -> Error(reason)
}
}
False -> Ok(#(result_accumulator, position_accumulator))
}
}

pub fn from_vector(from vec: vector.Vector)
{
do_from_vector(at: 0, from: vec, to: [])
}
Loading

0 comments on commit ac050e5

Please sign in to comment.