# 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