486 lines
11 KiB
Ruby
486 lines
11 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
require 'spec_helper'
|
||
|
|
||
|
# Simple examples taken from: https://blog.scottlogic.com/2018/04/26/webassembly-by-hand.html
|
||
|
|
||
|
RSpec.describe 'WAT code generation' do
|
||
|
let(:mod) { WAG::Module.new }
|
||
|
subject { mod }
|
||
|
|
||
|
context 'Basic example' do
|
||
|
let(:mod) do
|
||
|
super().tap do |mod|
|
||
|
mod.func do
|
||
|
result(:i32)
|
||
|
i32.const(42)
|
||
|
end
|
||
|
mod.export('helloWorld') do
|
||
|
func(0)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe '#to_wat' do
|
||
|
subject { mod.to_wat.to_s }
|
||
|
it { is_expected.to eq '(module (export "helloWorld" (func 0)) (func (result i32) (i32.const 42)))' }
|
||
|
end
|
||
|
|
||
|
describe '#to_wasm' do
|
||
|
subject { mod.to_wasm }
|
||
|
|
||
|
it { is_expected.to be_valid }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'Game of life example' do
|
||
|
let(:mod) do
|
||
|
super().tap do |mod|
|
||
|
mod.import('console', 'log') do
|
||
|
func(:log) do
|
||
|
param(:i32)
|
||
|
param(:i32)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
mod.memory(:mem, 1)
|
||
|
|
||
|
mod.table(16, :anyfunc)
|
||
|
|
||
|
mod.elem(0, :dead, :dead, :dead, :alive, :dead, :dead, :dead, :dead,
|
||
|
:dead, :dead, :dead, :alive, :alive, :dead, :dead, :dead,
|
||
|
:dead, :dead)
|
||
|
|
||
|
mod.func(:alive) do
|
||
|
result(:i32)
|
||
|
i32.const(1)
|
||
|
end
|
||
|
|
||
|
mod.func(:dead) do
|
||
|
result(:i32)
|
||
|
i32.const(0)
|
||
|
end
|
||
|
|
||
|
mod.func(:offsetFromCoordinate) do
|
||
|
param(:x, :i32)
|
||
|
param(:y, :i32)
|
||
|
result(:i32)
|
||
|
|
||
|
i32.add do
|
||
|
i32.mul do
|
||
|
i32.const(200)
|
||
|
local.get(:y)
|
||
|
end
|
||
|
i32.mul do
|
||
|
i32.const(4)
|
||
|
local.get(:x)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
mod.func(:setCell) do
|
||
|
param(:x, :i32)
|
||
|
param(:y, :i32)
|
||
|
param(:value, :i32)
|
||
|
|
||
|
i32.store do
|
||
|
call(:offsetFromCoordinate) do
|
||
|
local.get(:x)
|
||
|
local.get(:y)
|
||
|
end
|
||
|
local.get(:value)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
mod.func(:getCell) do
|
||
|
param(:x, :i32)
|
||
|
param(:y, :i32)
|
||
|
result(:i32)
|
||
|
|
||
|
if_ do
|
||
|
result(:i32)
|
||
|
block do
|
||
|
result(:i32)
|
||
|
i32.and do
|
||
|
call(:inRange) do
|
||
|
i32.const(0)
|
||
|
i32.const(50)
|
||
|
local.get(:x)
|
||
|
end
|
||
|
call(:inRange) do
|
||
|
i32.const(0)
|
||
|
i32.const(50)
|
||
|
local.get(:y)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
then_ do
|
||
|
i32.load8_u do
|
||
|
call(:offsetFromCoordinate) do
|
||
|
local.get(:x)
|
||
|
local.get(:y)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
else_ do
|
||
|
i32.const(0)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
mod.func(:liveNeighbourCount) do
|
||
|
param(:x, :i32)
|
||
|
param(:y, :i32)
|
||
|
result(:i32)
|
||
|
|
||
|
i32.const(0)
|
||
|
|
||
|
# add the cell value from x + 1, y
|
||
|
call(:isCellAlive) do
|
||
|
i32.add do
|
||
|
local.get(:x)
|
||
|
i32.const(1)
|
||
|
end
|
||
|
local.get(:y)
|
||
|
end
|
||
|
i32.add
|
||
|
|
||
|
# add the cell value from x - 1, y
|
||
|
call(:isCellAlive) do
|
||
|
i32.add do
|
||
|
local.get(:x)
|
||
|
i32.const(-1)
|
||
|
end
|
||
|
local.get(:y)
|
||
|
end
|
||
|
i32.add
|
||
|
|
||
|
# add the cell value from x, y - 1
|
||
|
call(:isCellAlive) do
|
||
|
local.get(:x)
|
||
|
i32.add do
|
||
|
local.get(:y)
|
||
|
i32.const(-1)
|
||
|
end
|
||
|
end
|
||
|
i32.add
|
||
|
|
||
|
# add the cell value from x - 1, y - 1
|
||
|
call(:isCellAlive) do
|
||
|
i32.add do
|
||
|
local.get(:x)
|
||
|
i32.const(-1)
|
||
|
end
|
||
|
i32.add do
|
||
|
local.get(:y)
|
||
|
i32.const(-1)
|
||
|
end
|
||
|
end
|
||
|
i32.add
|
||
|
|
||
|
# add the cell value from x + 1, y - 1
|
||
|
call(:isCellAlive) do
|
||
|
i32.add do
|
||
|
local.get(:x)
|
||
|
i32.const(1)
|
||
|
end
|
||
|
i32.add do
|
||
|
local.get(:y)
|
||
|
i32.const(-1)
|
||
|
end
|
||
|
end
|
||
|
i32.add
|
||
|
|
||
|
# add the cell value from x, y + 1
|
||
|
call(:isCellAlive) do
|
||
|
local.get(:x)
|
||
|
i32.add do
|
||
|
local.get(:y)
|
||
|
i32.const(1)
|
||
|
end
|
||
|
end
|
||
|
i32.add
|
||
|
|
||
|
# add the cell value from x - 1, y + 1
|
||
|
call(:isCellAlive) do
|
||
|
i32.add do
|
||
|
local.get(:x)
|
||
|
i32.const(-1)
|
||
|
end
|
||
|
i32.add do
|
||
|
local.get(:y)
|
||
|
i32.const(1)
|
||
|
end
|
||
|
end
|
||
|
i32.add
|
||
|
|
||
|
# add the cell value from x + 1, y + 1
|
||
|
call(:isCellAlive) do
|
||
|
i32.add do
|
||
|
local.get(:x)
|
||
|
i32.const(1)
|
||
|
end
|
||
|
i32.add do
|
||
|
local.get(:y)
|
||
|
i32.const(1)
|
||
|
end
|
||
|
end
|
||
|
i32.add
|
||
|
end
|
||
|
|
||
|
mod.func(:inRange) do
|
||
|
param(:low, :i32)
|
||
|
param(:high, :i32)
|
||
|
param(:value, :i32)
|
||
|
result(:i32)
|
||
|
|
||
|
i32.and do
|
||
|
i32.ge_s do
|
||
|
local.get(:value)
|
||
|
local.get(:low)
|
||
|
end
|
||
|
i32.lt_s do
|
||
|
local.get(:value)
|
||
|
local.get(:high)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
mod.func(:isCellAlive) do
|
||
|
param(:x, :i32)
|
||
|
param(:y, :i32)
|
||
|
result(:i32)
|
||
|
|
||
|
i32.and do
|
||
|
call(:getCell) do
|
||
|
local.get(:x)
|
||
|
local.get(:y)
|
||
|
end
|
||
|
i32.const(1)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
mod.func(:setCellStateForNextGeneration) do
|
||
|
param(:x, :i32)
|
||
|
param(:y, :i32)
|
||
|
param(:value, :i32)
|
||
|
|
||
|
call(:setCell) do
|
||
|
local.get(:x)
|
||
|
local.get(:y)
|
||
|
i32.or do
|
||
|
call(:isCellAlive) do
|
||
|
local.get(:x)
|
||
|
local.get(:y)
|
||
|
end
|
||
|
i32.shl do
|
||
|
local.get(:value)
|
||
|
i32.const(1)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
mod.func(:evolveCellToNextGeneration) do
|
||
|
param(:x, :i32)
|
||
|
param(:y, :i32)
|
||
|
|
||
|
call(:setCellStateForNextGeneration) do
|
||
|
local.get(:x)
|
||
|
local.get(:y)
|
||
|
call_indirect do
|
||
|
result(:i32)
|
||
|
i32.or do
|
||
|
i32.mul do
|
||
|
i32.const(9)
|
||
|
call(:isCellAlive) do
|
||
|
local.get(:x)
|
||
|
local.get(:y)
|
||
|
end
|
||
|
end
|
||
|
call(:liveNeighbourCount) do
|
||
|
local.get(:x)
|
||
|
local.get(:y)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
mod.func(:increment) do
|
||
|
param(:value, :i32)
|
||
|
result(:i32)
|
||
|
|
||
|
i32.add do
|
||
|
local.get(:value)
|
||
|
i32.const(1)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
mod.func(:evolveAllCells) do
|
||
|
local(:x, :i32)
|
||
|
local(:y, :i32)
|
||
|
|
||
|
local.set(:x) do
|
||
|
i32.const(0)
|
||
|
end
|
||
|
local.set(:y) do
|
||
|
i32.const(0)
|
||
|
end
|
||
|
|
||
|
block do
|
||
|
loop_ do
|
||
|
local.set(:x) do
|
||
|
i32.const(0)
|
||
|
end
|
||
|
|
||
|
block do
|
||
|
loop_ do
|
||
|
call(:evolveCellToNextGeneration) do
|
||
|
local.get(:x)
|
||
|
local.get(:y)
|
||
|
end
|
||
|
local.set(:x) do
|
||
|
call(:increment) do
|
||
|
local.get(:x)
|
||
|
end
|
||
|
end
|
||
|
br_if(1) do
|
||
|
i32.eq do
|
||
|
local.get(:x)
|
||
|
i32.const(50)
|
||
|
end
|
||
|
end
|
||
|
br(0)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local.set(:y) do
|
||
|
call(:increment) do
|
||
|
local.get(:y)
|
||
|
end
|
||
|
end
|
||
|
br_if(1) do
|
||
|
i32.eq do
|
||
|
local.get(:y)
|
||
|
i32.const(50)
|
||
|
end
|
||
|
end
|
||
|
br(0)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
mod.func(:promoteNextGeneration) do
|
||
|
local(:x, :i32)
|
||
|
local(:y, :i32)
|
||
|
|
||
|
local.set(:x) do
|
||
|
i32.const(0)
|
||
|
end
|
||
|
local.set(:y) do
|
||
|
i32.const(0)
|
||
|
end
|
||
|
|
||
|
block do
|
||
|
loop_ do
|
||
|
local.set(:x) do
|
||
|
i32.const(0)
|
||
|
end
|
||
|
|
||
|
block do
|
||
|
loop_ do
|
||
|
call(:setCell) do
|
||
|
local.get(:x)
|
||
|
local.get(:y)
|
||
|
i32.shr_u do
|
||
|
call(:getCell) do
|
||
|
local.get(:x)
|
||
|
local.get(:y)
|
||
|
end
|
||
|
i32.const(1)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local.set(:x) do
|
||
|
call(:increment) do
|
||
|
local.get(:x)
|
||
|
end
|
||
|
end
|
||
|
br_if(1) do
|
||
|
i32.eq do
|
||
|
local.get(:x)
|
||
|
i32.const(50)
|
||
|
end
|
||
|
end
|
||
|
br(0)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local.set(:y) do
|
||
|
call(:increment) do
|
||
|
local.get(:y)
|
||
|
end
|
||
|
end
|
||
|
br_if(1) do
|
||
|
i32.eq do
|
||
|
local.get(:y)
|
||
|
i32.const(50)
|
||
|
end
|
||
|
end
|
||
|
br(0)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
mod.func(:tick) do
|
||
|
call(:evolveAllCells)
|
||
|
call(:promoteNextGeneration)
|
||
|
end
|
||
|
|
||
|
mod.export('tick') do
|
||
|
func(:tick)
|
||
|
end
|
||
|
mod.export('promoteNextGeneration') do
|
||
|
func(:promoteNextGeneration)
|
||
|
end
|
||
|
mod.export('evolveAllCells') do
|
||
|
func(:evolveAllCells)
|
||
|
end
|
||
|
mod.export('evolveCellToNextGeneration') do
|
||
|
func(:evolveCellToNextGeneration)
|
||
|
end
|
||
|
mod.export('setCellStateForNextGeneration') do
|
||
|
func(:setCellStateForNextGeneration)
|
||
|
end
|
||
|
mod.export('isCellAlive') do
|
||
|
func(:isCellAlive)
|
||
|
end
|
||
|
mod.export('inRange') do
|
||
|
func(:inRange)
|
||
|
end
|
||
|
mod.export('offsetFromCoordinate') do
|
||
|
func(:offsetFromCoordinate)
|
||
|
end
|
||
|
mod.export('liveNeighbourCount') do
|
||
|
func(:liveNeighbourCount)
|
||
|
end
|
||
|
mod.export('getCell') do
|
||
|
func(:getCell)
|
||
|
end
|
||
|
mod.export('setCell') do
|
||
|
func(:setCell)
|
||
|
end
|
||
|
mod.export('memory') do
|
||
|
memory(:mem)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe '#to_wasm' do
|
||
|
subject { mod.to_wasm }
|
||
|
it { is_expected.to be_valid }
|
||
|
end
|
||
|
end
|
||
|
end
|