Update README and tidy up some loose ends.
This commit is contained in:
parent
aafde4b7a4
commit
780310c537
6 changed files with 176 additions and 56 deletions
150
README.md
150
README.md
|
@ -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).
|
||||
|
|
|
@ -19,6 +19,7 @@ module WAG
|
|||
alias else_ else
|
||||
alias end_ end
|
||||
alias loop_ loop
|
||||
alias return_ return
|
||||
|
||||
def f32
|
||||
WAG::F32Instructions.new(instructions)
|
||||
|
|
|
@ -14,7 +14,7 @@ module WAG
|
|||
def to_sexpr
|
||||
return [name, @result.to_sexpr] if @result
|
||||
|
||||
[name]
|
||||
name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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?('$')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue