-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ac050e5
Showing
17 changed files
with
832 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*.beam | ||
*.ez | ||
/build | ||
erl_crash.dump |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: []) | ||
} |
Oops, something went wrong.