Skip to content

Commit

Permalink
Merge branch 'simd'
Browse files Browse the repository at this point in the history
  • Loading branch information
dy committed Aug 15, 2024
2 parents 663da76 + ca3ff8a commit 43f7ada
Show file tree
Hide file tree
Showing 9 changed files with 1,075 additions and 188 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
},
"homepage": "https://github.com/audio-lab/watr#readme",
"devDependencies": {
"tst": "^7.1.1",
"tst": "^7.3.0",
"wabt": "^1.0.28",
"wat-compiler": "^1.0.0"
}
Expand Down
46 changes: 14 additions & 32 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,21 @@ Useful for hi-level languages or dynamic (in-browser) compilation.<br>

<!-- See [REPL](https://audio-lab.github.io/watr/repl.html).-->

&nbsp; | Size (gzipped) | Performance (op/s)
---|---|---
watr | 3.8 kb | 6000
[wat-compiler](https://github.com/stagas/wat-compiler) | 6 kb | 348
[wabt](https://github.com/AssemblyScript/wabt.js) | 300 kb | 574
&nbsp; | Size (gzipped) | Performance (op/s) | Features
---|---|---|---
watr | 3.8 kb | 6000 |
[wat-compiler](https://github.com/stagas/wat-compiler) | 6 kb | 348 |
[wassemble](https://github.com/wingo/wassemble) | ? kb | ? |
[wabt](https://github.com/AssemblyScript/wabt.js) | 300 kb | 574 |

## Usage

```js
import wat from 'watr'

// compile text to binary
const buffer = wat(`(func
(export "double") (param f64) (result f64)
(f64.mul (local.get 0) (f64.const 2))
)`)

// create instance
const module = new WebAssembly.Module(buffer)
const instance = new WebAssembly.Instance(module)

// use API
const {double} = instance.exports
double(108) // 216
```

## API

### Compile

Compiles wasm text or syntax tree into wasm binary.
Compile wasm text or syntax tree into wasm binary.

```js
import { compile } from 'watr'
import compile from 'watr' // or `import { compile } from 'watr'`

const buffer = compile(`(func (export "double")
(param f64) (result f64)
Expand Down Expand Up @@ -100,11 +81,12 @@ parse(`(func (export "double") (param f64) (result f64) (f64.mul (local.get 0) (
## Status

* [x] wasm core
* [x] multiple values
* [x] bulk memory ops (0 index)
* [ ] simd
* [ ] multiple memories
* [ ] func/ref types
* [x] [multi-value](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md)
* [x] [bulk memory ops](https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md)
* [~] [simd](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md)
* [ ] [multiple memories](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md)
* [ ] [func refs](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md)
* [ ] [gc](https://github.com/WebAssembly/gc)


<!--
Expand Down
102 changes: 76 additions & 26 deletions src/compile.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { uleb, leb, bigleb, f64, f32 } from './util.js'
import * as encode from './encode.js'
import { uleb } from './encode.js'
import { OP, SECTION, ALIGN, TYPE, KIND } from './const.js'
import parse from './parse.js'
import { err, TypedArray } from './util.js'

const OP_END = 0xb, OP_I32_CONST = 0x41, OP_I64_CONST = 0x42, OP_F32_CONST = 0x43, OP_F64_CONST = 0x44, OP_SKIP = 0x6, OP_GLOBAL_GET = 35, OP_GLOBAL_SET = 36

/**
* Converts a WebAssembly Text Format (WAT) tree to a WebAssembly binary format (Wasm).
Expand Down Expand Up @@ -121,11 +122,38 @@ const build = {

// NOTE: numeric comparison is faster than generic hash lookup

// v128s: (v128.load x) etc
// https://github.com/WebAssembly/simd/blob/master/proposals/simd/BinarySIMD.md
if (opCode >= 268) {
opCode -= 268
immed = [0xfd, ...uleb(opCode)]
// (v128.load)
if (opCode <= 0x0b) {
const o = consumeParams(args)
immed.push(Math.log2(o.align ?? ALIGN[op]), ...uleb(o.offset ?? 0))
}
// (v128.load_lane offset? align? idx)
else if (opCode >= 0x54 && opCode <= 0x5d) {
const o = consumeParams(args)
immed.push(Math.log2(o.align ?? ALIGN[op]), ...uleb(o.offset ?? 0))
// (v128.load_lane_zero)
if (opCode <= 0x5b) immed.push(...uleb(args.shift()))
}
// (v128.const i32x4), (i8x16.shuffle 0 1 ... 15 a b)
else if (opCode === 0x0c || opCode === 0x0d) {
immed.push(...consumeConst(op.split('.')[0], args))
}
// (i8x16.extract_lane_s 0 ...)
else if (opCode >= 0x15 && opCode <= 0x22) {
immed.push(...uleb(args.shift()))
}
opCode = null // ignore opcode
}

// bulk memory: (memory.init) (memory.copy) etc
// https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md#instruction-encoding
if (opCode >= 252) {
immed = [0xfc, opCode %= 252]
else if (opCode >= 252) {
immed = [0xfc, ...uleb(opCode -= 252)]
// memory.init idx, memory.drop idx, table.init idx, table.drop idx
if (!(opCode & 0b10)) immed.push(...uleb(args.shift()))
else immed.push(0)
Expand All @@ -134,20 +162,19 @@ const build = {
opCode = null // ignore opcode
}

// binary/unary - just consume immed
else if (opCode >= 69) { }
// binary/unary (i32.add a b) - no immed
else if (opCode >= 0x45) { }

// (i32.store align=n offset=m at value)
// (i32.store align=n offset=m at value) etc
else if (opCode >= 40 && opCode <= 62) {
// FIXME: figure out point in Math.log2 aligns
let o = { align: ALIGN[op], offset: 0 }, param
while (args[0]?.includes('=')) param = args.shift().split('='), o[param[0]] = Number(param[1])
immed = [Math.log2(o.align), ...uleb(o.offset)]
let o = consumeParams(args)
immed = [Math.log2(o.align ?? ALIGN[op]), ...uleb(o.offset ?? 0)]
}

// (i32.const 123)
else if (opCode >= 65 && opCode <= 68) {
immed = (opCode == 65 ? leb : opCode == 66 ? bigleb : opCode == 67 ? f32 : f64)(args.shift())
// (i32.const 123), (f32.const 123.45) etc
else if (opCode >= 0x41 && opCode <= 0x44) {
immed = encode[op.split('.')[0]](args.shift())
}

// (local.get $id), (local.tee $id x)
Expand All @@ -156,7 +183,7 @@ const build = {
}

// (global.get id), (global.set id)
else if (opCode == OP_GLOBAL_GET || opCode == OP_GLOBAL_SET) {
else if (opCode == 0x23 || opCode == 36) {
immed = uleb(args[0]?.[0] === '$' ? ctx.global[args.shift()] : args.shift())
}

Expand Down Expand Up @@ -264,7 +291,7 @@ const build = {
return () => {
const bytes = []
while (body.length) consume(body, bytes)
ctx.code.push([...uleb(bytes.length + 2 + locTypes.length), ...uleb(locTypes.length >> 1), ...locTypes, ...bytes, OP_END])
ctx.code.push([...uleb(bytes.length + 2 + locTypes.length), ...uleb(locTypes.length >> 1), ...locTypes, ...bytes, 0x0b])
}
},

Expand All @@ -284,7 +311,7 @@ const build = {
let name = args[0][0] === '$' && args.shift()
if (name) ctx.global[name] = ctx.global.length
let [type, init] = args, mut = type[0] === 'mut' ? 1 : 0
ctx.global.push([TYPE[mut ? type[1] : type], mut, ...iinit(init)])
ctx.global.push([TYPE[mut ? type[1] : type], mut, ...initGlobal(init)])
},

// (table 1 2? funcref)
Expand All @@ -299,7 +326,7 @@ const build = {
// (elem (i32.const 0) $f1 $f2), (elem (global.get 0) $f1 $f2)
elem([, offset, ...elems], ctx) {
const tableIdx = 0 // FIXME: table index can be defined
ctx.elem.push([tableIdx, ...iinit(offset, ctx), ...uleb(elems.length), ...elems.flatMap(el => uleb(el[0] === '$' ? ctx.func[el] : el))])
ctx.elem.push([tableIdx, ...initGlobal(offset, ctx), ...uleb(elems.length), ...elems.flatMap(el => uleb(el[0] === '$' ? ctx.func[el] : el))])
},

// (export "name" (kind $name|idx))
Expand Down Expand Up @@ -352,7 +379,7 @@ const build = {
if (!offset && !mem) offset = inits.shift()
if (!offset) offset = ['i32.const', 0]

ctx.data.push([0, ...iinit(offset, ctx), ...str(inits.map(i => i[0] === '"' ? i.slice(1, -1) : i).join(''))])
ctx.data.push([0, ...initGlobal(offset, ctx), ...str(inits.map(i => i[0] === '"' ? i.slice(1, -1) : i).join(''))])
},

// (start $main)
Expand All @@ -362,14 +389,32 @@ const build = {
}

// (i32.const 0), (global.get idx) - instantiation time initializer
const iinit = ([op, literal], ctx) =>
op === 'f32.const' ? [OP_F32_CONST, ...f32(literal), OP_END] :
op === 'f64.const' ? [OP_F64_CONST, ...f64(literal), OP_END] :
op === 'i32.const' ? [OP_I32_CONST, ...leb(literal), OP_END] :
op === 'i64.const' ? [OP_I64_CONST, ...bigleb(literal), OP_END] :
op === 'global.get' ? [OP_GLOBAL_GET, ...uleb(literal[0] === '$' ? ctx.global[literal] : literal), OP_END] :
err(`Unknown init ${op} ${literal}`)
const initGlobal = ([op, literal, ...args], ctx) => {
if (op === 'global.get') return [0x23, ...uleb(literal[0] === '$' ? ctx.global[literal] : literal), 0x0b]
const [type] = op.split('.')
// (v128.const i32x4 1 2 3 4)
return [...(type === 'v128' ? [0xfd, 0x0c] : [0x41 + ['i32', 'i64', 'f32', 'f64'].indexOf(type)]), ...consumeConst(type, [literal, ...args]), 0x0b]
}

// consume cost, no op type
const consumeConst = (type, args) => {
// (v128.const i32x4 1 2 3 4), (i8x16.shuffle 1 2 ... 15)
if (type === 'v128' || type === 'i8x16') {
let [t, n] = (type === 'v128' ? args.shift() : type).split('x'),
bytes = new Uint8Array(16),
arr = new TypedArray[t](bytes.buffer)

for (let i = 0; i < n; i++) {
arr[i] = encode[t].parse(args.shift())
}

return bytes
}
// (i32.const 1)
return encode[type](args[0])
}

// escape codes
const escape = { n: 10, r: 13, t: 9, v: 1, '\\': 92 }

// build string binary
Expand Down Expand Up @@ -415,4 +460,9 @@ const consumeType = (nodes, ctx) => {
return [idx, params, result]
}

const err = text => { throw Error(text) }
// consume align/offset/etc params
const consumeParams = (args) => {
let params = {}, param
while (args[0]?.includes('=')) param = args.shift().split('='), params[param[0]] = Number(param[1])
return params
}
17 changes: 15 additions & 2 deletions src/const.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 43f7ada

Please sign in to comment.