# 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().build do func do result(:i32) i32.const(42) end 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().build do import('console', 'log') do func(:log) do param(:i32) param(:i32) end end memory(:mem, 1) table(16, :anyfunc) elem(0, :dead, :dead, :dead, :alive, :dead, :dead, :dead, :dead, :dead, :dead, :dead, :alive, :alive, :dead, :dead, :dead, :dead, :dead) func(:alive) do result(:i32) i32.const(1) end func(:dead) do result(:i32) i32.const(0) end 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 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 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 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 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 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 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 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 func(:increment) do param(:value, :i32) result(:i32) i32.add do local.get(:value) i32.const(1) end end 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 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 func(:tick) do call(:evolveAllCells) call(:promoteNextGeneration) end export('tick') do func(:tick) end export('promoteNextGeneration') do func(:promoteNextGeneration) end export('evolveAllCells') do func(:evolveAllCells) end export('evolveCellToNextGeneration') do func(:evolveCellToNextGeneration) end export('setCellStateForNextGeneration') do func(:setCellStateForNextGeneration) end export('isCellAlive') do func(:isCellAlive) end export('inRange') do func(:inRange) end export('offsetFromCoordinate') do func(:offsetFromCoordinate) end export('liveNeighbourCount') do func(:liveNeighbourCount) end export('getCell') do func(:getCell) end export('setCell') do func(:setCell) end 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