Update README and tidy up some loose ends.

This commit is contained in:
James Harton 2020-04-02 10:05:22 +13:00
parent aafde4b7a4
commit 780310c537
6 changed files with 176 additions and 56 deletions

150
README.md
View file

@ -1,28 +1,146 @@
# WAG - The WebAssembly Code Generator
This Ruby gem allows you to generate WebAssembly programs programatically.
This Ruby gem allows you to generate WebAssembly programs programmatically using
a DSL.
## Example
It is closely modeled after WAT, the WebAssembly text format, and at this stage
generates WAT and compiles and validates it using [the WebAssembly Binary Toolkit][1].
Due to the flexibility of WAT this library is very flexible in what structures
it allows you to create. Be aware that you can build modules which are not
valid WASM. Always validate your modules by using `#to_wasm.valid?`.
## Keyword conflict
Any WASM instructions whose name conflicts with a Ruby keyword (eg `loop`,
`return`, etc) are also aliased with a underscore suffix for use in the DSL. The
methods defining the original names are also there, so can be used by the likes
of `public_send`, etc.
## Folding
WAG supports generating both the "folded" and "unfolded" variants of the WAT
language. As an example here are two implementations of Euclid's Greatest
Common Divisor algorithm:
### Example of unfolded generation
```ruby
WAG::Module.new.build do
memory(1)
export('memory', :memory)
export('hello', :$hello)
func(:$hello) do
unfolded = WAG::Module.new.build do
func(:gcd) do
param(:a, :i32)
param(:b, :i32)
result(:i32)
i32.const(16)
local(:r, :i32)
block
loop_
# let r = a % b
local.get(:a)
local.get(:b)
i32.rem_s
local.set(:r)
# let a = b and b = R
local.get(:b)
local.set(:a)
local.get(:r)
local.set(:b)
# if a % b == 0, return b
local.get(:a)
local.get(:b)
i32.rem_s
i32.eqz
br_if 1
br 0
end_
end_
local.get(:b)
return_
end
data do
i32.const(16)
<< "Hello World\00"
export("gcd").func(:gcd)
end
```
### Example of folded generation
```ruby
folded = WAG::Module.new.build do
func(:gcd) do
param(:a, :i32)
param(:b, :i32)
result(:i32)
local(:r, :i32)
block do
loop_ do
# let r = a % b
local.set(:r) do
i32.rem_s do
local.get(:a)
local.get(:b)
end
end
# let a = b and b = R
local.set(:a) do
local.get(:b)
end
local.set(:b) do
local.get(:r)
end
# if a % b == 0, return b
br_if(1) do
i32.eqz do
i32.rem_s do
local.get(:a)
local.get(:b)
end
end
end
br 0
end
end
return_ do
local.get(:b)
end
end
export("gcd") do
func(:gcd)
end
end
```
Both modules emit identical WASM bytecode and produce the same answers:
```ruby
folded.to_wasm.save("folded.wasm")
unfolded.to_wasm.save("unfolded.wasm")
```
```bash
$ sha256sum folded.wasm unfolded.wasm
0023ef97eba001226401e432912f2e644a6cbef107ba183546c51177eee46e2c folded.wasm
0023ef97eba001226401e432912f2e644a6cbef107ba183546c51177eee46e2c unfolded.wasm
$ wasmtime folded.wasm --invoke gcd 270 192
6
$ wasmtime unfolded.wasm --invoke gcd 270 192
6
```
1: https://github.com/WebAssembly/wabt
## Installation
Add this line to your application's Gemfile:
Add this line to your application's `Gemfile`:
```ruby
gem 'wag'
@ -36,9 +154,6 @@ Or install it yourself as:
$ gem install wag
## Usage
TODO: Write usage instructions here
## Development
@ -48,9 +163,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/wag.
Bug reports and pull requests are welcome on GitLab at https://gitlab.com/jimsy/wag.
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
The gem is available as open source under the terms of the [Hippocratic License](https://firstdonoharm.dev/version/2/1/license.html).

View file

@ -19,6 +19,7 @@ module WAG
alias else_ else
alias end_ end
alias loop_ loop
alias return_ return
def f32
WAG::F32Instructions.new(instructions)

View file

@ -14,7 +14,7 @@ module WAG
def to_sexpr
return [name, @result.to_sexpr] if @result
[name]
name
end
end
end

View file

@ -8,7 +8,7 @@ module WAG
def initialize(value)
if value.is_a?(Integer)
@value = @value.to_i
@value = value.to_i
else
value = value.to_s
@value = if value.start_with?('$')

View file

@ -16,6 +16,11 @@ module WAG
@types = []
end
def build(&block)
instance_exec(&block)
self
end
def import(module_name, name, &block)
import = Import.new(module_name, name)
@imports << import

View file

@ -10,12 +10,12 @@ RSpec.describe 'WAT code generation' do
context 'Basic example' do
let(:mod) do
super().tap do |mod|
mod.func do
super().build do
func do
result(:i32)
i32.const(42)
end
mod.export('helloWorld') do
export('helloWorld') do
func(0)
end
end
@ -35,33 +35,33 @@ RSpec.describe 'WAT code generation' do
context 'Game of life example' do
let(:mod) do
super().tap do |mod|
mod.import('console', 'log') do
super().build do
import('console', 'log') do
func(:log) do
param(:i32)
param(:i32)
end
end
mod.memory(:mem, 1)
memory(:mem, 1)
mod.table(16, :anyfunc)
table(16, :anyfunc)
mod.elem(0, :dead, :dead, :dead, :alive, :dead, :dead, :dead, :dead,
:dead, :dead, :dead, :alive, :alive, :dead, :dead, :dead,
:dead, :dead)
elem(0, :dead, :dead, :dead, :alive, :dead, :dead, :dead, :dead,
:dead, :dead, :dead, :alive, :alive, :dead, :dead, :dead,
:dead, :dead)
mod.func(:alive) do
func(:alive) do
result(:i32)
i32.const(1)
end
mod.func(:dead) do
func(:dead) do
result(:i32)
i32.const(0)
end
mod.func(:offsetFromCoordinate) do
func(:offsetFromCoordinate) do
param(:x, :i32)
param(:y, :i32)
result(:i32)
@ -78,7 +78,7 @@ RSpec.describe 'WAT code generation' do
end
end
mod.func(:setCell) do
func(:setCell) do
param(:x, :i32)
param(:y, :i32)
param(:value, :i32)
@ -92,7 +92,7 @@ RSpec.describe 'WAT code generation' do
end
end
mod.func(:getCell) do
func(:getCell) do
param(:x, :i32)
param(:y, :i32)
result(:i32)
@ -128,7 +128,7 @@ RSpec.describe 'WAT code generation' do
end
end
mod.func(:liveNeighbourCount) do
func(:liveNeighbourCount) do
param(:x, :i32)
param(:y, :i32)
result(:i32)
@ -228,7 +228,7 @@ RSpec.describe 'WAT code generation' do
i32.add
end
mod.func(:inRange) do
func(:inRange) do
param(:low, :i32)
param(:high, :i32)
param(:value, :i32)
@ -246,7 +246,7 @@ RSpec.describe 'WAT code generation' do
end
end
mod.func(:isCellAlive) do
func(:isCellAlive) do
param(:x, :i32)
param(:y, :i32)
result(:i32)
@ -260,7 +260,7 @@ RSpec.describe 'WAT code generation' do
end
end
mod.func(:setCellStateForNextGeneration) do
func(:setCellStateForNextGeneration) do
param(:x, :i32)
param(:y, :i32)
param(:value, :i32)
@ -281,7 +281,7 @@ RSpec.describe 'WAT code generation' do
end
end
mod.func(:evolveCellToNextGeneration) do
func(:evolveCellToNextGeneration) do
param(:x, :i32)
param(:y, :i32)
@ -307,7 +307,7 @@ RSpec.describe 'WAT code generation' do
end
end
mod.func(:increment) do
func(:increment) do
param(:value, :i32)
result(:i32)
@ -317,7 +317,7 @@ RSpec.describe 'WAT code generation' do
end
end
mod.func(:evolveAllCells) do
func(:evolveAllCells) do
local(:x, :i32)
local(:y, :i32)
@ -371,7 +371,7 @@ RSpec.describe 'WAT code generation' do
end
end
mod.func(:promoteNextGeneration) do
func(:promoteNextGeneration) do
local(:x, :i32)
local(:y, :i32)
@ -433,45 +433,45 @@ RSpec.describe 'WAT code generation' do
end
end
mod.func(:tick) do
func(:tick) do
call(:evolveAllCells)
call(:promoteNextGeneration)
end
mod.export('tick') do
export('tick') do
func(:tick)
end
mod.export('promoteNextGeneration') do
export('promoteNextGeneration') do
func(:promoteNextGeneration)
end
mod.export('evolveAllCells') do
export('evolveAllCells') do
func(:evolveAllCells)
end
mod.export('evolveCellToNextGeneration') do
export('evolveCellToNextGeneration') do
func(:evolveCellToNextGeneration)
end
mod.export('setCellStateForNextGeneration') do
export('setCellStateForNextGeneration') do
func(:setCellStateForNextGeneration)
end
mod.export('isCellAlive') do
export('isCellAlive') do
func(:isCellAlive)
end
mod.export('inRange') do
export('inRange') do
func(:inRange)
end
mod.export('offsetFromCoordinate') do
export('offsetFromCoordinate') do
func(:offsetFromCoordinate)
end
mod.export('liveNeighbourCount') do
export('liveNeighbourCount') do
func(:liveNeighbourCount)
end
mod.export('getCell') do
export('getCell') do
func(:getCell)
end
mod.export('setCell') do
export('setCell') do
func(:setCell)
end
mod.export('memory') do
export('memory') do
memory(:mem)
end
end