├── .gitignore
├── .gitmodules
├── LICENSE.txt
├── Makefile
├── README.md
├── bin
└── translate.js
├── package.json
├── rollup.config.js
├── src
├── Instance.js
├── Memory.js
├── Module.js
├── Table.js
├── compile.js
├── constants.js
├── errors.js
├── index.js
├── stdlib.js
├── translate
│ ├── funcode.js
│ ├── index.js
│ ├── input.js
│ └── result.js
└── utils.js
├── tests
└── spec.js
└── webextension
├── inject-wasm-polyfill.js
└── manifest.json
/.gitignore:
--------------------------------------------------------------------------------
1 | test.tmp.*
2 | node_modules/
3 | wasm-polyfill.min.js
4 | webextension/wasm-polyfill.min.js
5 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "spec"]
2 | path = spec
3 | url = https://github.com/WebAssembly/spec
4 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Ryan Kelly
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | .PHONY: all
3 | all: wasm-polyfill.min.js webextension
4 |
5 | # This uses rollup to make a single-file bundle,
6 | # then lightly hacks it to avoid clobbering an existing WebAssembly global.
7 | wasm-polyfill.min.js: src/*.js src/translate/*.js node_modules/long/package.json rollup.config.js
8 | ./node_modules/.bin/rollup -c
9 | sed -i '' 's/\([a-z][a-z]*\)\.WebAssembly *= *\([a-z][a-z]*\)()/\1.WebAssembly=\1.WebAssembly||\2()/g' wasm-polyfill.min.js
10 |
11 | spec/interpreter/README.md:
12 | git submodule update --init
13 |
14 | spec/interpreter/wasm: spec/interpreter/README.md .git/modules/spec/*
15 | cd ./spec/interpreter && make
16 |
17 | node_modules/long/package.json:
18 | npm install
19 |
20 | .PHONY: webextension
21 | webextension: ./webextension/wasm-polyfill.min.js
22 |
23 | ./webextension/wasm-polyfill.min.js: wasm-polyfill.min.js
24 | cp ./wasm-polyfill.min.js ./webextension/wasm-polyfill.min.js
25 |
26 | .PHONY: test
27 | test: wasm-polyfill.min.js spec/interpreter/wasm
28 | ./node_modules/.bin/mocha --timeout 10000 ./tests/
29 |
30 | .PHONY: test-bail
31 | test-bail: wasm-polyfill.min.js spec/interpreter/wasm
32 | ./node_modules/.bin/mocha --timeout 10000 --bail ./tests/
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Status: Unmaintained
3 | ====================
4 |
5 | [](http://unmaintained.tech/)
6 |
7 | I am [no longer actively maintaining this project](https://rfk.id.au/blog/entry/archiving-open-source-projects/).
8 |
9 |
10 | A highly-experimental, likely-pretty-slow polyfill for WebAssembly
11 | ==================================================================
12 |
13 | I want to learn about the binary encoding and execution semantics of
14 | WebAssembly, and trying to write a polyfill seemed like as good a way
15 | to do that as any. I make no promises about this ever being useful
16 | for Real Work. But it's fun!
17 |
18 | In this repository we have:
19 |
20 | * `src/*.js`: the polyfill, the core of which is a hand-written parser for
21 | WASM that translates it into javascript for execution.
22 |
23 | * `spec/`: the https://github.com/WebAssembly/spec repo as a git submodule,
24 | to make it easy to run the tests.
25 |
26 | * `tests/`: a little test harness to run the spec tests using the polyfill.
27 |
28 | * `webextension/`: a small webextension that injects the polyfill into
29 | every webpage, so you can pretend that your browser has
30 | native WebAssembly support. It's useful for running
31 | third-party content like the AngryBots demo.
32 |
33 | * `Makefile`: provides the following targets for your convenience:
34 |
35 | * `make`: build a standaline minified polyfill file,
36 | and corresponding webexenstion bundle.
37 | * `make test`: run the full WebAssembly spec test suite
38 | * `make test JS_ENGINE=/path/to/js`: run tests with a specific JS engine,
39 | e.g. spidermonkey instead of node.
40 |
41 |
42 |
43 | Current Status
44 | --------------
45 |
46 | It works, but it's pretty slow.
47 |
48 | When run under node, the polyfill passes the full spec interpreter test
49 | suite. When run under spidermonkey it passes all but some float-related
50 | tests, and I suspect that's because the tests need to be updated to account
51 | for different handling of NaNs [1].
52 |
53 | The code can also load and run the WASM
54 | [AngryBots demo](http://webassembly.org/demo/).
55 | It currently runs so slowly as to be nigh unplayable,
56 | but there's still some low-hanging fruit to improve performance
57 | of the generated JavaScript; see below for some ideas..
58 |
59 | [1] https://github.com/WebAssembly/spec/issues/286
60 |
61 |
62 | How to Use
63 | ----------
64 |
65 | First, consider whether this highly-experimental code is right for
66 | you. If you're just looking to compile some code to the web and
67 | have it run, you'll almost certainly be better served by the more
68 | mature WebAssembly support in the emscripten toolchain:
69 |
70 | https://github.com/kripken/emscripten/wiki/WebAssembly
71 |
72 | But if you're trying to do something unusual, like JIT to WASM at
73 | runtime in the browser, them a full-blown polyfill may be necessary.
74 |
75 | Next, make sure you've built it into a standalone JS file::
76 |
77 | ```
78 | > make
79 | ```
80 |
81 | Then you can load it into a webpage like this:
82 |
83 | ```
84 |
85 |
90 | ```
91 |
92 | Or load it as a module in node:
93 |
94 | ```
95 | var WebAssembly = require('wasm-polyfill.min.js')
96 | var funcs = WebAssembly.instantiate("wasm code here")
97 | ```
98 |
99 | Or if you're feel really adventurous, you can load
100 | `./webextension/manifest.json` as a WebExtension
101 | in [Firefox](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Temporary_Installation_in_Firefox)
102 | or [Chrome](https://developer.chrome.com/extensions/getstarted#unpacked)
103 | and have it available on all webpages.
104 |
105 | I've used this trick to successfully load and run the
106 | [AngryBots demo](http://webassembly.org/demo/), albeit slowly.
107 |
108 |
109 | Theory of Operation
110 | -------------------
111 |
112 | The polyfill works by parsing the WebAssembly binary format and
113 | translating each function into semantically-equivalent javascript.
114 | The translation process prioritizes the following, in order:
115 |
116 | 1. The generated code must be *semantically correct*, even for various
117 | edge-cases where the semantics of WASM do not map cleanly onto the
118 | semantics of JavaScript.
119 |
120 | 2. The generated code should *run fast*. We try to generate as close
121 | to valid asmjs as possible, and will spend CPU cycles during translation
122 | if they result in significantly improved code (such as eliminating
123 | lots of bounds checks).
124 |
125 | 3. The code generation should be done quickly, since we're running on the
126 | client. This means we won't try to do a lot of micro-optimization of
127 | the generated code.
128 |
129 | So far it does a good job of (1), but there's a lot of work remaining
130 | on (2), and I haven't looked into (3) much at all. But hey, it's early
131 | days for this code! :-)
132 |
133 | As a concrete example, given this simple WebAssembly implementation of a
134 | 32-bit factorial function:
135 |
136 | ```
137 | (module
138 | (func (export "fac-rec") (param i64) (result i64)
139 | (if i64 (i64.eq (get_local 0) (i64.const 0))
140 | (i64.const 1)
141 | (i64.mul (get_local 0) (call 0 (i64.sub (get_local 0) (i64.const 1))))
142 | )
143 | )
144 | )
145 | ```
146 |
147 | The polyfill will produce a JavaScript function that looks like:
148 |
149 | ```
150 | function (WebAssembly, asmlib, imports) {
151 |
152 | // Create and annotate the exported functions.
153 | var funcs = asmfuncs(asmlib, imports)
154 |
155 | funcs.F0._wasmTypeSigStr = 'i_i'
156 | funcs.F0._wasmJSWrapper = null
157 |
158 | var exports = {}
159 | exports['fac-rec'] = funcs.F0
160 |
161 | // An inner asmjs-style function containing all the code.
162 | // In this case we're able to generate valid asmjs, but
163 | // that's not always the case in general.
164 |
165 | function asmfuncs(stdlib, foreign, heap) {
166 | "use asm"
167 |
168 | var i32_mul = foreign.i32_mul
169 |
170 | function F0(li0) {
171 | li0 = li0|0
172 | var ti0 = 0
173 | var ti1 = 0
174 | var ti2 = 0
175 | if ((((li0)|0)==((0)|0))|0) { L1: do {
176 | ti0 = (1)|0
177 | } while(0)} else { L1: do {
178 | ti1 = (li0)|0
179 | ti2 = (F0((((li0)|0)-((1)|0))|0))|0
180 | ti0 = (i32_mul((ti1)|0,(ti2)|0))|0
181 | } while(0) }
182 | return ti0
183 | }
184 |
185 | return {
186 | F0: F0
187 | }
188 | }
189 |
190 | // If there were initializers to run, they'd go here.
191 |
192 | return exports
193 | }
194 | ```
195 |
196 | For this simple function, we're able to generate something that's
197 | valid asmjs and should run pretty fast. That's not always the case
198 | in general. Some of the tricky parts, where WASM differs from asmjs
199 | and makes a direct translation difficult, include:
200 |
201 | * WASM has growable memory, while this feature was not successfully
202 | added to the asmjs spec. In the general case we use a similar approach
203 | to emscripten's `ALLOW_MEMORY_GROWTH` option, with a callback that
204 | triggers us to take fresh references to the memory.
205 |
206 | However, if the WASM module declares a fixed memory size, we know
207 | that memory growth is not possible and can generate valid asmjs.
208 |
209 | * WASM has native 64-bit integers, javascript does not. For now
210 | we're just using a third-party `Long` class to handle them, which
211 | is likely pretty slow but gives the correct semantics.
212 |
213 | In the future it might be interesting to try to decompose them into
214 | pairs of 32-bit values, but that would significantly complicate the
215 | code generation logic.
216 |
217 | * WASM requires that out-of-bounds memory accesses trap, while javascript
218 | allows them to succeed and return zero. In the general case we have
219 | to emit bounds-checks before each memory access.
220 |
221 | I'm working towards doing some primitive range analysis to omit duplicate
222 | bounds checks, but it's not complete yet.
223 |
224 | * WASM requires that mis-aligned memory accesses succeed, while asmjs will just
225 | read at the nearest aligned address. In the general case we have to check
226 | alignment before each memory access and call out to a helper function if
227 | it's incorrect.
228 |
229 | I don't have a good idea for reducing the overhead of this check in practice;
230 | it would be nice if the mis-aligned access were allowed to trap rather than
231 | succeeding.
232 |
233 | * WASM requires that memory be little-endian, but TypedArrays reflect the
234 | the endianness of the underlying platform. We feature-detect endianness and
235 | fall back to doing unaligned reads via a DataView on big-endian platforms.
236 |
237 | * WASM defines precise semantics for the bit patterns inside a NaN, and
238 | requires that e.g. abs() and neg() preserve them. By contrast, JavaScript
239 | engines are allowed to ruthlessly canonicalize NaNs and many of them do.
240 |
241 | In the general case we work around this by boxing NaNs into `new Number()`
242 | instances, and attaching the precise bit-pattern as a property. But we
243 | can avoid this overhead when it's possible to prove that the bit pattern
244 | will never be observed (e.g. because it's immediately passed to an operator
245 | that's allowed to canonicalize it).
246 |
247 | * WASM function pointers share a single, mutable tablespace, so we currently
248 | do a bunch of runtime type checks when they're being invoked. It's likely
249 | worth trying to internally segregate them by type signature in the same way
250 | that asmjs does, but I haven't yet tried to do so.
251 |
252 |
--------------------------------------------------------------------------------
/bin/translate.js:
--------------------------------------------------------------------------------
1 | //
2 | // Ahead-of-time translate a .wasm file to JS.
3 | // The JS can't run by itself without all of the
4 | // other runtime support stuff provided by this
5 | // module, but it's handy for testing/debugging.
6 | //
7 | // Usage: node ./bin/translate.js filename.wasm
8 | //
9 |
10 |
11 | fs = require('fs')
12 | var WebAssembly = require('../wasm-polyfill.min.js')
13 |
14 | var buf = fs.readFileSync(process.argv[2])
15 | var data = new Uint8Array(buf)
16 | var r = WebAssembly._translate(data)
17 |
18 | process.stdout.write(new Buffer(r.bytes))
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wasm-polyfill",
3 | "version": "0.1.0",
4 | "devDependencies": {
5 | "mocha": "^3.2.0",
6 | "rollup": "^0.39.2",
7 | "rollup-plugin-commonjs": "^7.0.0",
8 | "rollup-plugin-node-resolve": "^2.0.0",
9 | "rollup-plugin-uglify": "^1.0.1"
10 | },
11 | "dependencies": {
12 | "long": "^3.2.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 |
2 | import commonjs from 'rollup-plugin-commonjs'
3 | import nodeResolve from 'rollup-plugin-node-resolve'
4 | import uglify from 'rollup-plugin-uglify'
5 |
6 | export default {
7 | moduleName: 'WebAssembly',
8 | entry: 'src/index.js',
9 | dest: 'wasm-polyfill.min.js',
10 | format: 'umd',
11 | plugins: [
12 | nodeResolve({
13 | module: true,
14 | browser: true
15 | }),
16 | commonjs(),
17 | uglify()
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/src/Instance.js:
--------------------------------------------------------------------------------
1 | //
2 | // The `Instance` object.
3 | //
4 | // This object takes a compiled module and provides the
5 | // necessary run-time data to make live function objects.
6 | // It's where we coordinate the details of passing values
7 | // back-and-forth between JS and WASM.
8 | //
9 |
10 | import WebAssembly from "./index"
11 |
12 | import Module from "./Module"
13 | import Table from "./Table"
14 | import Memory from "./Memory"
15 | import stdlib from "./stdlib"
16 | import { SECTIONS, EXTERNAL_KINDS } from "./constants"
17 | import { LinkError, RuntimeError } from "./errors"
18 | import {
19 | assertIsDefined,
20 | assertIsInstance,
21 | assertIsCallable,
22 | ToJSValue,
23 | ToWASMValue,
24 | makeSigStr,
25 | dump
26 | } from "./utils"
27 |
28 |
29 | export default function Instance(moduleObject, importObject) {
30 | assertIsDefined(this)
31 | assertIsInstance(moduleObject, Module)
32 | if (typeof importObject !== "undefined") {
33 | if (typeof importObject !== "object") {
34 | throw new TypeError()
35 | }
36 | }
37 |
38 | // Collect, type-check and coerce the imports.
39 |
40 | var r = moduleObject._compiled
41 | var imports = {}
42 | var numFuncsDone = 0
43 | var numGlobalsDone = 0
44 | r.imports.forEach(function(i) {
45 | var o = importObject[i.module_name]
46 | assertIsInstance(o, Object)
47 | var v = o[i.item_name]
48 | if (typeof v === "undefined") {
49 | throw new LinkError("cannot import undefined")
50 | }
51 | switch(i.kind) {
52 | case EXTERNAL_KINDS.FUNCTION:
53 | assertIsCallable(v, LinkError)
54 | if (! v._wasmRawFunc) {
55 | // If this is not a function from another WASM instance, then we
56 | // have to convert args and return values between WASM and JS semantics.
57 | // XXX TODO: we could probably make a more efficient translation layer...
58 | var typ = r.types[i.type]
59 | imports["F" + numFuncsDone] = function() {
60 | var args = []
61 | var origArgs = arguments
62 | typ.param_types.forEach(function(param_typ, idx) {
63 | args.push(ToJSValue(origArgs[idx], param_typ))
64 | })
65 | var res = v.apply(undefined, args)
66 | if (typ.return_types.length > 0) {
67 | res = ToWASMValue(res, typ.return_types[0])
68 | }
69 | return res
70 | }
71 | imports["F" + numFuncsDone]._origFunc = v
72 | } else {
73 | // If importing functions from another WASM instance,
74 | // we can shortcut *and* we can do more typechecking.
75 | if (v._wasmRawFunc._wasmTypeSigStr !== makeSigStr(r.types[i.type])) {
76 | throw new LinkError("function import type mis-match")
77 | }
78 | imports["F" + numFuncsDone] = v._wasmRawFunc
79 | }
80 | numFuncsDone++
81 | break
82 | case EXTERNAL_KINDS.GLOBAL:
83 | imports["G" + numGlobalsDone] = ToWASMValue(v, i.type.content_type, LinkError)
84 | numGlobalsDone++
85 | break
86 | case EXTERNAL_KINDS.MEMORY:
87 | assertIsInstance(v, Memory, LinkError)
88 | if (v._internals.current < i.type.limits.initial) {
89 | throw new LinkError("memory import too small")
90 | }
91 | if (i.type.limits.maximum) {
92 | if (v._internals.current > i.type.limits.maximum) {
93 | throw new LinkError("memory import too big")
94 | }
95 | if (!v._internals.maximum || v._internals.maximum > i.type.limits.maximum) {
96 | throw new LinkError("memory import has too large a maximum")
97 | }
98 | }
99 | imports["M0"] = v
100 | break
101 | case EXTERNAL_KINDS.TABLE:
102 | assertIsInstance(v, Table, LinkError)
103 | if (v.length < i.type.limits.initial) {
104 | throw new LinkError("table import too small")
105 | }
106 | if (i.type.limits.maximum) {
107 | if (v.length > i.type.limits.maximum) {
108 | throw new LinkError("table import too big")
109 | }
110 | if (!v._internals.maximum || v._internals.maximum > i.type.limits.maximum) {
111 | throw new LinkError("table import has too large a maximum")
112 | }
113 | }
114 | imports["T0"] = v
115 | break
116 | default:
117 | throw new LinkError("unexpected import kind: " + i.kind)
118 | }
119 | })
120 |
121 | Object.keys(stdlib).forEach(function(key) {
122 | imports[key] = stdlib[key]
123 | })
124 |
125 | // Instantiate the compiled javascript module, which will give us all the exports.
126 | var asmlib = {
127 | Int8Array: Int8Array,
128 | Int16Array: Int16Array,
129 | Int32Array: Int32Array,
130 | Uint8Array: Uint8Array,
131 | Uint16Array: Uint16Array,
132 | Uint32Array: Uint32Array,
133 | Float32Array: Float32Array,
134 | Float64Array: Float64Array,
135 | Math: Math
136 | }
137 | this._exports = r.jsmodule(WebAssembly, asmlib, imports)
138 | this.exports = {}
139 | var self = this;
140 | r.exports.forEach(function(e) {
141 | switch (e.kind) {
142 | case EXTERNAL_KINDS.FUNCTION:
143 | var wasmFunc = self._exports[e.field]
144 | // Wrap exported functions to convert between JS and WASM value semantics.
145 | // We cache the wrapper on the function.
146 | if (!wasmFunc._wasmJSWrapper) {
147 | wasmFunc._wasmJSWrapper = function () {
148 | // Type-check and coerce arguments.
149 | // XXX TODO: we could probably use raw type info rather than sigstr here.
150 | // XXX TODO: can we come up with a more efficient system for this, one
151 | // that doesn't use the `arguments` object in common cases?
152 | var args = []
153 | ARGLOOP: for (var i = 0; i < wasmFunc._wasmTypeSigStr.length; i++) {
154 | switch (wasmFunc._wasmTypeSigStr.charAt(i)) {
155 | case 'i':
156 | args.push(arguments[i]|0)
157 | break
158 | case 'l':
159 | throw new RuntimeError("cannot pass i64 from js: " + arguments[i])
160 | case 'f':
161 | args.push(Math.fround(+arguments[i]))
162 | break
163 | case 'd':
164 | args.push(+arguments[i])
165 | break
166 | case '_':
167 | break ARGLOOP
168 | default:
169 | throw new RuntimeError("malformed _wasmTypeSigStr")
170 | }
171 | }
172 | return wasmFunc.apply(this, args)
173 | }
174 | wasmFunc._wasmJSWrapper._wasmRawFunc = wasmFunc
175 | }
176 | self.exports[e.field] = wasmFunc._wasmJSWrapper
177 | break
178 | default:
179 | self.exports[e.field] = self._exports[e.field]
180 | }
181 | })
182 | }
183 |
--------------------------------------------------------------------------------
/src/Memory.js:
--------------------------------------------------------------------------------
1 | //
2 | // The `Memory` object.
3 | //
4 | // We do the best we can to immitate the growable memory objects
5 | // from WASM on top of normal ArrayBuffers.
6 | //
7 | // Of particular interest here, is the use of `_onChange` callbacks
8 | // to trigger reconstruction of the memory state of any linked
9 | // Instances. We expect changes to be rare so it seems valuable
10 | // to have the Instance take a local reference to the bufer and
11 | // replace it when necessary, rather than always dereferencing it
12 | // afresh from the Memory object.
13 | //
14 |
15 | import { PAGE_SIZE } from "./constants"
16 | import {
17 | assertIsDefined,
18 | assertIsInstance,
19 | assertIsType,
20 | ToNonWrappingUint32
21 | } from "./utils"
22 |
23 |
24 | export default function Memory(memoryDescriptor) {
25 | assertIsDefined(this)
26 | assertIsType(memoryDescriptor, "object")
27 | var initial = ToNonWrappingUint32(memoryDescriptor.initial)
28 | var maximum = null
29 | if (memoryDescriptor.hasOwnProperty("maximum")) {
30 | maximum = ToNonWrappingUint32(memoryDescriptor.maximum)
31 | }
32 | this._internals = {
33 | buffer: new ArrayBuffer(initial * PAGE_SIZE),
34 | initial: initial,
35 | current: initial,
36 | maximum: maximum,
37 | callbacks: []
38 | }
39 | }
40 |
41 | //
42 | // Register a callback to be executed when the underlying
43 | // memory buffer changes.
44 | //
45 | // XXX TODO: can we use weakrefs for this, to avoid
46 | // the Memory keeping all connected instances alive?
47 | // I suspect not, but worth investigating.
48 | //
49 | Memory.prototype._onChange = function _onChange(cb) {
50 | this._internals.callbacks.push(cb)
51 | }
52 |
53 | Memory.prototype.grow = function grow(delta) {
54 | var oldSize = this._grow(delta)
55 | if (oldSize < 0) {
56 | throw new RangeError()
57 | }
58 | return oldSize
59 | }
60 |
61 | Memory.prototype._grow = function _grow(delta) {
62 | assertIsInstance(this, Memory)
63 | var oldSize = this._internals.current
64 | delta = ToNonWrappingUint32(delta)
65 | if (delta > 65536) {
66 | return -1
67 | }
68 | var newSize = oldSize + delta
69 | if (this._internals.maximum) {
70 | if (newSize > this._internals.maximum) {
71 | return -1
72 | }
73 | }
74 | if (newSize > 65536) {
75 | return -1
76 | }
77 | var newBuffer = new ArrayBuffer(newSize * PAGE_SIZE)
78 | // XXX TODO more efficient copy of the old buffer?
79 | new Uint8Array(newBuffer).set(new Uint8Array(this._internals.buffer))
80 | // XXX TODO: cleanly detach the old buffer
81 | this._internals.buffer = newBuffer
82 | this._internals.current = newSize
83 | // Notify listeners that things have changed.
84 | this._internals.callbacks.forEach(function (cb){
85 | cb()
86 | })
87 | return oldSize
88 | }
89 |
90 | Object.defineProperty(Memory.prototype, "buffer", {
91 | // XXX TODO: do I need to do anything to prevent ths buffer
92 | // from being detached by code that gets it?
93 | get: function() {
94 | assertIsInstance(this, Memory)
95 | return this._internals.buffer
96 | }
97 | })
98 |
--------------------------------------------------------------------------------
/src/Module.js:
--------------------------------------------------------------------------------
1 | //
2 | // The `Module` object.
3 | //
4 | // This object coordinates parsing the WASM bytecode
5 | // and providing it for linking as live function objects.
6 | // It also lets you introspect some details of the module
7 | //
8 |
9 | import { assertIsDefined, assertIsInstance } from "./utils"
10 | import { EXTERNAL_KIND_NAMES, SECTIONS } from "./constants"
11 | import { compileSync } from "./compile"
12 |
13 |
14 | export default function Module(bytesOrCompiledModule) {
15 | assertIsDefined(this)
16 | if (typeof bytesOrCompiledModule.jsmodule === "undefined") {
17 | this._compiled = compileSync(bytesOrCompiledModule)
18 | } else {
19 | this._compiled = bytesOrCompiledModule
20 | }
21 | }
22 |
23 | Module.exports = function exports(moduleObject) {
24 | assertIsInstance(moduleObject, Module)
25 | return this._compiled.exports.map(function(e) {
26 | return {
27 | name: e.field, // XXX TODO: convert from utf8
28 | kind: EXTERNAL_KIND_NAMES[e.kind]
29 | }
30 | })
31 | }
32 |
33 | Module.imports = function imports(moduleObject) {
34 | assertIsInstance(moduleObject, Module)
35 | return this._compiled.imports.map(function(i) {
36 | return {
37 | module: i.module_name, // XXX TODO: convert from utf8
38 | name: i.item_name, // XXX TODO: convert from utf8
39 | kind: EXTERNAL_KIND_NAMES[i.kind]
40 | }
41 | })
42 | }
43 |
44 | Module.customSections = function imports(moduleObject, sectionName) {
45 | assertIsInstance(moduleObject, Module)
46 | throw new RuntimeError('customSections not implemented yet')
47 | }
48 |
--------------------------------------------------------------------------------
/src/Table.js:
--------------------------------------------------------------------------------
1 | //
2 | // The `Table` object.
3 | //
4 | // This is a straightforward wrapper around a plain old
5 | // JavaScript array.
6 | //
7 |
8 | import { LinkError, RuntimeError } from "./errors"
9 | import {
10 | assertIsDefined,
11 | assertIsInstance,
12 | assertIsType,
13 | ToNonWrappingUint32
14 | } from "./utils"
15 |
16 |
17 | export default function Table(tableDescriptor) {
18 | assertIsDefined(this)
19 | assertIsType(tableDescriptor, "object")
20 | var initial = ToNonWrappingUint32(tableDescriptor.initial)
21 | var maximum = null
22 | if (tableDescriptor.hasOwnProperty("maximum")) {
23 | maximum = ToNonWrappingUint32(tableDescriptor.maximum)
24 | }
25 | var values = new Array(initial)
26 | for (var i = 0; i < initial; i++) {
27 | values[i] = null
28 | }
29 | this._internals = {
30 | values: values,
31 | initial: initial,
32 | maximum: maximum
33 | }
34 | }
35 |
36 | Object.defineProperty(Table.prototype, "length", {
37 | get: function() {
38 | assertIsInstance(this, Table)
39 | return this._internals.values.length
40 | }
41 | })
42 |
43 | Table.prototype.grow = function grow(delta) {
44 | assertIsInstance(this, Table)
45 | var oldSize = this.length
46 | var newSize = oldSize + ToNonWrappingUint32(delta)
47 | if (newSize < oldSize) {
48 | throw new RangeError()
49 | }
50 | if (this._internals.maximum !== null) {
51 | if (newSize > this._internals.maximum) {
52 | throw new RangeError()
53 | }
54 | }
55 | for (var i = oldSize; i < newSize; i++) {
56 | this._internals.values.push(null);
57 | }
58 | return oldSize
59 | }
60 |
61 | Table.prototype.get = function get(index) {
62 | assertIsInstance(this, Table)
63 | index = ToNonWrappingUint32(index)
64 | if (index >= this._internals.values.length) {
65 | throw RangeError
66 | }
67 | return this._internals.values[index]
68 | }
69 |
70 | Table.prototype.set = function set(index, value) {
71 | assertIsInstance(this, Table)
72 | index = ToNonWrappingUint32(index)
73 | if (index >= this._internals.values.length) {
74 | throw RangeError
75 | }
76 | this._internals.values[index] = value
77 | }
78 |
79 | Table.prototype._get = function get(index) {
80 | var entry = this._internals.values[index]
81 | if (! entry) {
82 | throw new RuntimeError("invalid table entry at " + index)
83 | }
84 | return entry
85 | }
86 |
87 | Table.prototype._setmany = function _setmany(start, values) {
88 | assertIsInstance(this, Table)
89 | start = ToNonWrappingUint32(start)
90 | if ((start + values.length) > this._internals.values.length) {
91 | throw new LinkError("table set out of bounds")
92 | }
93 | for (var i = 0; i < values.length; i++) {
94 | this._internals.values[start + i] = values[i]
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/compile.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | import translate from "./translate/index"
4 | import { dump, filename } from "./utils"
5 |
6 | //
7 | // Synchronously compile the given WASM bytes
8 | // into runnable javascript. This will likely
9 | // block the JS event loop for a significant
10 | // amount of time, so you should prefer to use
11 | // the `compileAsync` function below.
12 | //
13 | export function compileSync(bufferSource) {
14 | var bytes = new Uint8Array(arrayBufferFromBufferSource(bufferSource))
15 | var r = translate(bytes)
16 | // dump("------")
17 | // dump((new Buffer(r.bytes)).toString())
18 | // dump("------")
19 | r.jsmodule = loadSync(r.bytes)
20 | return r
21 | }
22 |
23 | function loadSync(jsBytes) {
24 | var codeStr = "return "
25 | // Use TextDecoder if available in the browser,
26 | // use Buffer when we're in nodejs,
27 | // and fall back to a big ol' loop when in doubt.
28 | if (typeof TextDecoder !== "undefined") {
29 | codeStr += (new TextDecoder("utf-8").decode(jsBytes))
30 | } else if (typeof Buffer !== "undefined") {
31 | codeStr += (new Buffer(jsBytes)).toString()
32 | } else {
33 | for (var i = 0; i < jsBytes.length; i++) {
34 | codeStr += String.fromCharCode(jsBytes[i])
35 | }
36 | }
37 | return new Function(codeStr)()
38 | }
39 |
40 | //
41 | // Asynchronously compile the given WASM bytes
42 | // into runnable javascript. This tries to launch
43 | // a Worker to perform the translation, and to use
44 | // the DOM to evaluabe the JS asynchronously.
45 | // It will fall back to a synchronous implementation
46 | // if either of the above fails.
47 | //
48 |
49 | var asyncCompileCounter = 0
50 |
51 | export function compileAsync(bufferSource) {
52 | var canUseWorker =
53 | typeof window !== "undefined" &&
54 | typeof Worker !== "undefined" &&
55 | typeof Blob !== "undefined" &&
56 | typeof URL !== "undefined" &&
57 | typeof URL.createObjectURL && "undefined";
58 | // If Workers are not available, fall back to synchronous.
59 | // Note that we must compile bytes as they are when the
60 | // function is called, meaning we must do it before
61 | // yielding the event loop.
62 | if (! canUseWorker) {
63 | try {
64 | return Promise.resolve(compileSync(bufferSource))
65 | } catch (err) {
66 | return Promise.reject(err)
67 | }
68 | }
69 | // Looks like we can compile in a worker.
70 | // We use an inline script to keep things self-contained.
71 | try {
72 | var bytes = new Uint8Array(arrayBufferFromBufferSource(bufferSource))
73 | var resolve, reject
74 | var p = new Promise(function(rslv, rjct) {
75 | resolve = rslv
76 | reject = rjct
77 | })
78 | var workerSrc = new Blob([
79 | "importScripts('" + filename() + "')\n",
80 | "\n" +
81 | "onmessage = function(e) {\n" +
82 | " var bytes = e.data.bytes\n" +
83 | " try {\n" +
84 | " var r = WebAssembly._translate(bytes)\n" +
85 | " // Non-copying transfer of buffer back to main code.\n" +
86 | " postMessage({ result: r }, [r.buffer])\n" +
87 | " } catch (err) {\n" +
88 | " postMessage({ error: err })\n" +
89 | " }\n" +
90 | "}"
91 | ])
92 | var workerURL = URL.createObjectURL(workerSrc)
93 | var worker = new Worker(workerURL)
94 | worker.onerror = function (err) {
95 | URL.revokeObjectURL(workerURL)
96 | reject(err)
97 | }
98 | worker.onmessage = function(evt) {
99 | worker.terminate()
100 | URL.revokeObjectURL(workerURL)
101 | if (evt.data.error) {
102 | reject(evt.data.error)
103 | } else {
104 | resolve(evt.data.result)
105 | }
106 | }
107 | // This copies the bytes into the worker.
108 | // It's important to do this before yielding the event loop,
109 | // because calling code is free to mutate the bytes after
110 | // this function returns.
111 | worker.postMessage({ bytes: bytes })
112 | return p.then(function(r) {
113 | // Now, can we load the JS asynchronously as well?
114 | // This may not be possible if we're e.g. running inside
115 | // one Worker and offloading translation to another.
116 | var canUseScriptTag =
117 | typeof document !== "undefined" &&
118 | typeof document.createElement !== "undefined" &&
119 | typeof document.body !== "undefined" &&
120 | typeof document.body.appendChild !== "undefined";
121 | if (! canUseScriptTag) {
122 | r.jsmodule = loadSync(r.bytes)
123 | return r
124 | }
125 | // Yes! Create a