├── .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 | [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](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