├── .gitignore ├── LICENSE ├── README.md ├── bench ├── README.md ├── contended-lock-worker.js ├── contended-lock1.html ├── contended-lock4.html ├── futex-master.js ├── futex-worker.js ├── futex.html ├── lock.js ├── scrool.js ├── static-server.js ├── store-vs-exchange.js ├── synchronic-master.js ├── synchronic-polyfill.js ├── synchronic-worker.js ├── synchronic.html ├── uncontended-lock1-worker.js ├── uncontended-lock1.html ├── uncontended-lock4.html └── worker-common.js ├── bleeding-edge ├── condtest-master.js ├── condtest-worker.js ├── condtest.html ├── futex.js ├── futextest.js ├── lock.js ├── locktest-master.js ├── locktest-worker.js └── locktest.html ├── demo ├── README.md ├── mandelbrot-animation-simd │ ├── mandelbrot-master.js │ ├── mandelbrot-parameters.js │ ├── mandelbrot-worker.js │ └── mandelbrot.html ├── mandelbrot-animation │ ├── mandelbrot-master.js │ ├── mandelbrot-parameters.js │ ├── mandelbrot-worker.js │ └── mandelbrot.html ├── mandelbrot-animation2 │ ├── mandelbrot-master.js │ ├── mandelbrot-parameters.js │ ├── mandelbrot-worker.js │ └── mandelbrot.html ├── ray-flatjs │ ├── Makefile │ ├── README.md │ ├── expected-output.ppm │ ├── ray-common.flat_js │ ├── ray-common.js │ ├── ray-master.flat_js │ ├── ray-master.js │ ├── ray-worker.flat_js │ ├── ray-worker.js │ └── ray.html └── renderWorld │ ├── README.md │ ├── renderWorld-kernel.js │ ├── renderWorld-worker.js │ ├── renderWorld.html │ └── renderWorld.js ├── doc └── TUTORIAL.md ├── src ├── arena.js ├── asymmetric-barrier.js ├── asymmetric-futex.js ├── asymmetric-intqueue.js ├── asymmetric-synchronic.js ├── barrier.js ├── buffer.js ├── bump-alloc.js ├── channel.js ├── float64atomics.js ├── int64atomics.js ├── intqueue.js ├── lock.js ├── marshaler.js ├── message.js ├── par.js └── synchronic.js ├── test ├── bump-alloc-shelltest.js ├── channel-shelltest.js ├── float64atomics-shelltest.js ├── int64atomics-shelltest.js ├── intqueue-shelltest.js ├── marshaler-shelltest.js ├── scrool.js ├── synchronic-shelltest.js ├── test-asymmetric-barrier-master.js ├── test-asymmetric-barrier-worker.js ├── test-asymmetric-barrier.html ├── test-asymmetric-futex-master.js ├── test-asymmetric-futex-worker.js ├── test-asymmetric-futex.html ├── test-asymmetric-intqueue-m2w-master.js ├── test-asymmetric-intqueue-m2w-worker.js ├── test-asymmetric-intqueue-m2w.html ├── test-asymmetric-synchronic-master.js ├── test-asymmetric-synchronic-worker.js ├── test-asymmetric-synchronic.html ├── test-barrier-master.js ├── test-barrier-worker.js ├── test-barrier.html ├── test-buffer-master.js ├── test-buffer-worker.js ├── test-buffer.html ├── test-float64atomics-master.js ├── test-float64atomics-worker.js ├── test-float64atomics.html ├── test-lock-master.js ├── test-lock-worker.js ├── test-lock.html ├── test-messageport-master.js ├── test-messageport-worker.js ├── test-messageport.html ├── test-par-master.js ├── test-par-worker.js ├── test-par.html ├── test-postmsg-master.js ├── test-postmsg-worker.js ├── test-postmsg.html ├── test-sendint-master.js ├── test-sendint-worker.js ├── test-sendint.html ├── test-sendmsg-master.js ├── test-sendmsg-sibling1.js ├── test-sendmsg-sibling2.js ├── test-sendmsg-siblings.html ├── test-sendmsg-siblings.js ├── test-sendmsg-worker.js └── test-sendmsg.html └── util ├── asmjs.js ├── canvas.js ├── numWorkers.js └── shim.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parlib-simple UNMAINTAINED 2 | 3 | NOTE: This library is no longer being maintained. 4 | 5 | This is a simple library providing synchronization and work 6 | distribution abstractions, assuming as little as possible about the 7 | client code (and thus providing minimal help with data abstraction and 8 | so on). The fact that shared memory is provided as flat arrays of 9 | primitive values is exposed in all constructors, for example. 10 | 11 | These all work in Firefox Nightly. 12 | 13 | Each file usually lists the other files it needs to be included. 14 | 15 | See doc/ for tutorials, etc. 16 | 17 | See demo/ for some programs that use these data structures. 18 | 19 | See test/ for more programs, not tutorial in nature, that use these 20 | data structures. 21 | 22 | ## High level facilities 23 | 24 | * load-balancing data parallel framework (par.js) 25 | * messaging system with value marshaling (channel.js) 26 | 27 | ## Mid-level facilities 28 | 29 | * shared-memory queue for bundles of integer values (intqueue.js) 30 | * shared-memory queue for single primitive values (buffer.js) 31 | * shared-memory "bump" memory allocator (bump-alloc.js) 32 | 33 | ## Low-level facilities 34 | 35 | * signaling facility for atomic values (synchronic.js) 36 | * barrier synchronization (barrier.js) 37 | * asymmetric master/worker barrier synchronization (asymmetric-barrier.js) 38 | * atomics polyfill for SharedFloat64Array (float64atomics.js) 39 | * atomics polyfill for "int64" values on SharedInt32Array (int64atomics.js) 40 | * classical locks and condition variables (lock.js) 41 | 42 | ## Utilities 43 | 44 | * marshaling and unmarshaling of values (marshaler.js) 45 | * simple allocation superstructure for array buffers (arena.js) 46 | -------------------------------------------------------------------------------- /bench/README.md: -------------------------------------------------------------------------------- 1 | Some simple benchmarks for JS shared memory and atomics. 2 | 3 | The performance aspects we can test are these: 4 | 5 | * Simple atomic operations such as load, store, compareExchange, add. 6 | The performance is primarily affected by type inference and inlining 7 | in the JIT, subsequently by loop and flow optimizations applied to 8 | the code that results: hoisting and commoning of checks (range 9 | checks, checks that the memory is shared), removal of unnecessary 10 | barriers, and probably alias analysis. 11 | * Wait and Wake operations (futex operations now, synchronic operations later). 12 | We can measure the cost of various kinds of synchronization patterns that 13 | require the workers to go into wait/wake operations. 14 | 15 | -------------------------------------------------------------------------------- /bench/contended-lock-worker.js: -------------------------------------------------------------------------------- 1 | importScripts("../util/shim.js", "lock.js"); 2 | 3 | var ia, lockOffs, countOffs, iterations, lock, _; 4 | 5 | onmessage = function (ev) { 6 | if (ev.data[0] == "setup") { 7 | [_, ia, lockOffs, countOffs, iterations] = ev.data; 8 | lock = new Lock(ia, lockOffs); 9 | return; 10 | } 11 | 12 | let then = Date.now(); 13 | for ( let i=0 ; i < iterations ; i++ ) { 14 | lock.lock(); 15 | 16 | // "Small" workload 17 | //ia[countOffs]++; 18 | 19 | // "Medium" workload 20 | //ia[countOffs]+=g(h(i)); 21 | 22 | // "Large" workload 23 | ia[countOffs]+=fib(i&7); 24 | 25 | lock.unlock(); 26 | } 27 | let now = Date.now(); 28 | postMessage("Time to execute: " + (now - then) + "ms"); 29 | } 30 | 31 | function g(x) { 32 | return (x ^ 1) + 1; 33 | } 34 | 35 | function h(x) { 36 | return (x & 1) | 1; 37 | } 38 | 39 | function fib(n) { 40 | if (n < 2) 41 | return n; 42 | return fib(n-1) + fib(n-2); 43 | } 44 | -------------------------------------------------------------------------------- /bench/contended-lock1.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |
7 | 8 | 9 | 10 | 11 |This test creates two workers that repeatedly enter and leave the 12 | same contended critical region and update a shared variable.
13 | 14 | 15 | 16 | 17 | 18 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /bench/contended-lock4.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |This test creates some number of pairs of workers that repeatedly 12 | enter and leave contended critical regions, one region per 13 | pair.
14 | 15 | 16 | 17 | 18 | 19 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /bench/futex-master.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // This is the "simplest possible" message passing benchmark: it has 6 | // one integer location for data and one for synchronization, and two 7 | // workers use futex operations on the sync location to coordinate 8 | // access to the data location. 9 | 10 | var iterations = 200000; 11 | 12 | var bufSize = 1024; 13 | var syncOffset = 512; 14 | var workOffset = 0; 15 | var sab = new SharedArrayBuffer(bufSize); 16 | 17 | for ( var i=0 ; i < 2 ; i++ ) { 18 | var w = new Worker("futex-worker.js"); 19 | w.onmessage = function (ev) { msg(ev.data) } 20 | w.postMessage([sab, syncOffset, workOffset, iterations, i]); 21 | } 22 | -------------------------------------------------------------------------------- /bench/futex-worker.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | importScripts("worker-common.js"); 6 | 7 | onmessage = 8 | function (ev) { 9 | var [sab, sabIdx, locIdx, iterations, who] = ev.data; 10 | 11 | var s = new Int32Array(sab, sabIdx, 2); // "sync" locations (signal, counter) 12 | var iab = new Int32Array(sab, locIdx, 1); // "work" location 13 | 14 | var start = Date.now(); 15 | 16 | msg("Worker " + who + " ready"); 17 | var x = 0; 18 | for ( var i=0 ; i < iterations ; i++ ) { 19 | if (who == 0) { 20 | expectUpdate(s, 0, x); 21 | x++; 22 | iab[0]++; 23 | storeNotify(s, 0, ++x); 24 | } 25 | else { 26 | iab[0]++; 27 | storeNotify(s, 0, ++x); 28 | expectUpdate(s, 0, x); 29 | x++; 30 | } 31 | } 32 | msg("Worker " + who + " done"); 33 | 34 | if (who == 1) { 35 | assertEqual(iterations*2, s[0]); 36 | assertEqual(iterations*2, iab[0]); 37 | msg(Math.round(1000 * (2*iterations) / (Date.now() - start)) + " messages/s"); 38 | } 39 | 40 | msg("Worker " + who + " exiting"); 41 | }; 42 | 43 | function expectUpdate(s, idx, current) { 44 | if (spinWait(s, idx, current)) 45 | return; 46 | while (Atomics.load(s, idx) == current) { 47 | // These counters are *hugely* important for performance when 48 | // using Atomics.pause(), which might indicate that the futex 49 | // implementation needs some work! The counter radically 50 | // reduces the number of calls to wake(). It makes sense 51 | // that it should be faster not to call wake(), since most 52 | // of the time that call is not necessary - the spinning was 53 | // enough. Even so, can we do something? Can we add this 54 | // accounting to the futex system, for example? 55 | // 56 | // (In a lock situation we can spin also on unlock, to see if 57 | // somebody grabs the lock, but that does not apply here.) 58 | Atomics.add(s, idx+1, 1); 59 | Atomics.wait(s, idx, current); 60 | Atomics.sub(s, idx+1, 1); 61 | } 62 | } 63 | 64 | function storeNotify(s, idx, value) { 65 | Atomics.store(s, idx, value); 66 | if (Atomics.load(s, idx+1)) 67 | Atomics.wake(s, idx, 1); 68 | } 69 | 70 | function spinWaitPause(s, idx, current) { 71 | for (let i=0 ;; i++ ) { 72 | if (Atomics.load(s, idx) != current) 73 | return true; 74 | if (!Atomics.pause(i)) 75 | return false; 76 | } 77 | } 78 | 79 | function spinWaitNoPause(s, idx, current) {} 80 | 81 | var spinWait = (Atomics.pause ? spinWaitPause : spinWaitNoPause); 82 | -------------------------------------------------------------------------------- /bench/futex.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /bench/lock.js: -------------------------------------------------------------------------------- 1 | // This locking code is specialized for the benchmarks and can be 2 | // customized in several ways, tweak the constants below. 3 | // 4 | // To test the impact of eagerly calling wake(), move the call to 5 | // this._wake() in unlock() out of the conditional. Normally you want 6 | // withCounting to be false in that case. 7 | 8 | // Set withPause to true to use Atomics.pause to avoid going into Atomics.wait. 9 | // Requires the patch that defines Atomics.pause. 10 | 11 | const withPause = false; 12 | 13 | // Set withCounting to true to use a counter for the number of waiters to avoid 14 | // calling Atomics.wake. 15 | 16 | const withCounting = false; 17 | 18 | function Lock(ia, offs) { 19 | this.ia = ia; 20 | this.offs = offs; 21 | } 22 | 23 | Lock.init = function (ia, offs) { 24 | Atomics.store(ia, offs, 0); // lockState 25 | Atomics.store(ia, offs+1, 0); // numWaiters (optimization) 26 | Atomics.store(ia, offs+2, 0); // numWaits (for profiling) 27 | } 28 | 29 | Lock.prototype.lock = function () { 30 | let ia = this.ia; 31 | let offs = this.offs; 32 | let c = 0; 33 | if ((c = Atomics.compareExchange(ia, offs, 0, 1)) != 0) { 34 | do { 35 | if (c == 2 || Atomics.compareExchange(ia, offs, 1, 2) != 0) 36 | this._wait(); 37 | } while ((c = Atomics.compareExchange(ia, offs, 0, 2)) != 0); 38 | } 39 | }; 40 | 41 | Lock.prototype.unlock = function () { 42 | let ia = this.ia; 43 | let offs = this.offs|0; 44 | let c = Atomics.sub(ia, offs, 1); 45 | if (c != 1) { 46 | //Atomics.store(ia, offs, 0); 47 | Atomics.exchange(ia, offs, 0); // A lot faster, bug 1077027 48 | this._wake(); 49 | } 50 | }; 51 | 52 | function waitPauseCounting() { 53 | let ia = this.ia; 54 | let offs = this.offs; 55 | let i=0; 56 | while (Atomics.pause(i++)) 57 | if (Atomics.load(ia, offs) != 2) 58 | return; 59 | Atomics.add(ia, offs+1, 1); 60 | Atomics.add(ia, offs+2, 1); // Profiling 61 | Atomics.wait(ia, offs, 2); 62 | Atomics.sub(ia, offs+1, 1); 63 | } 64 | 65 | function wakePauseCounting() { 66 | let ia = this.ia; 67 | let offs = this.offs; 68 | if (Atomics.load(ia, offs+1) > 0) 69 | Atomics.wake(ia, offs, 1); 70 | } 71 | 72 | function waitPause() { 73 | let ia = this.ia; 74 | let offs = this.offs; 75 | let i=0; 76 | while (Atomics.pause(i++)) 77 | if (Atomics.load(ia, offs) != 2) 78 | return; 79 | Atomics.add(ia, offs+2, 1); // Profiling 80 | Atomics.wait(ia, offs, 2); 81 | } 82 | 83 | function wakePause() { 84 | let ia = this.ia; 85 | let offs = this.offs; 86 | Atomics.wake(ia, offs, 1); 87 | } 88 | 89 | function waitCounting() { 90 | let ia = this.ia; 91 | let offs = this.offs; 92 | Atomics.add(ia, offs+1, 1); 93 | Atomics.add(ia, offs+2, 1); // Profiling 94 | Atomics.wait(ia, offs, 2); 95 | Atomics.sub(ia, offs+1, 1); 96 | } 97 | 98 | function wakeCounting() { 99 | let ia = this.ia; 100 | let offs = this.offs; 101 | if (Atomics.load(ia, offs+1) > 0) 102 | Atomics.wake(ia, offs, 1); 103 | } 104 | 105 | function wait() { 106 | let ia = this.ia; 107 | let offs = this.offs; 108 | Atomics.add(ia, offs+2, 1); // Profiling 109 | Atomics.wait(ia, offs, 2); 110 | } 111 | 112 | var wake = wakePause; 113 | 114 | Lock.prototype._wait = (function () { 115 | if (pause && counting) 116 | return waitPauseCounting; 117 | if (pause) 118 | return waitPause; 119 | if (counting) 120 | return waitCounting; 121 | return wait; 122 | })(); 123 | 124 | Lock.prototype._wake = (function () { 125 | if (pause && counting) 126 | return wakePauseCounting; 127 | if (pause) 128 | return wakePause; 129 | if (counting) 130 | return wakeCounting; 131 | return wake; 132 | })(); 133 | 134 | -------------------------------------------------------------------------------- /bench/scrool.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Simple output abstraction. Call 'msg(s)' to print s in a message view. 6 | 7 | document.write(""); 8 | const scrool = document.getElementById("scrool"); 9 | 10 | function msg(s) { 11 | var d = document.createElement("div"); 12 | d.appendChild(document.createTextNode(s)); 13 | scrool.appendChild(d); 14 | } 15 | -------------------------------------------------------------------------------- /bench/static-server.js: -------------------------------------------------------------------------------- 1 | // Serve files from this directory. 2 | // 3 | // Useful for testing with Chrome, which does not allow workers to be 4 | // created from the file: space. 5 | 6 | // Source: https://gist.github.com/ryanflorence/701407, with fixes. 7 | 8 | var http = require("http"), 9 | url = require("url"), 10 | path = require("path"), 11 | fs = require("fs") 12 | port = process.argv[2] || 8888; 13 | 14 | http.createServer(function(request, response) { 15 | 16 | var uri = url.parse(request.url).pathname 17 | , filename = path.join(process.cwd(), uri); 18 | 19 | fs.exists(filename, function(exists) { 20 | if(!exists) { 21 | response.writeHead(404, {"Content-Type": "text/plain"}); 22 | response.write("404 Not Found\n"); 23 | response.end(); 24 | return; 25 | } 26 | 27 | if (fs.statSync(filename).isDirectory()) filename += '/index.html'; 28 | 29 | fs.readFile(filename, "binary", function(err, file) { 30 | if(err) { 31 | response.writeHead(500, {"Content-Type": "text/plain"}); 32 | response.write(err + "\n"); 33 | response.end(); 34 | return; 35 | } 36 | 37 | response.writeHead(200); 38 | response.write(file, "binary"); 39 | response.end(); 40 | }); 41 | }); 42 | }).listen(parseInt(port, 10)); 43 | 44 | console.log("Static file server running at\n => http://localhost:" + port + "/\nCTRL + C to shutdown"); 45 | -------------------------------------------------------------------------------- /bench/store-vs-exchange.js: -------------------------------------------------------------------------------- 1 | var ia = new Int32Array(new SharedArrayBuffer(1024)); 2 | 3 | function f1() { 4 | for ( let i=0 ; i < 10000 ; i++ ) 5 | Atomics.exchange(ia, 0, 0); 6 | } 7 | function g1() { 8 | for ( let i=0 ; i < 1000 ; i++ ) 9 | f1(); 10 | } 11 | 12 | function f2() { 13 | for ( let i=0 ; i < 10000 ; i++ ) 14 | Atomics.store(ia, 0, 0); 15 | } 16 | function g2() { 17 | for ( let i=0 ; i < 1000 ; i++ ) 18 | f2(); 19 | } 20 | 21 | print("Executing 10e6 Atomics.store and Atomics.exchange operations"); 22 | 23 | var then = Date.now(); 24 | g1(); 25 | var now = Date.now(); 26 | 27 | print("Exchange: " + (now - then) + "ms"); 28 | 29 | var then = Date.now(); 30 | g2(); 31 | var now = Date.now(); 32 | 33 | print("Store: " + (now - then) + "ms"); 34 | -------------------------------------------------------------------------------- /bench/synchronic-master.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // This is the "simplest possible" message passing benchmark: it has 6 | // one integer location for data and one for synchronization, and two 7 | // workers use synchronic operations on the sync location to 8 | // coordinate access to the data location. 9 | 10 | var iterations = 1000000; 11 | 12 | var bufSize = 1024; 13 | var syncOffset = 512; 14 | var polyOffset = 800; 15 | var workOffset = 0; 16 | var sab = new SharedArrayBuffer(bufSize); 17 | 18 | for ( var i=0 ; i < 2 ; i++ ) { 19 | var w = new Worker("synchronic-worker.js"); 20 | w.onmessage = function (ev) { msg(ev.data) } 21 | w.postMessage([sab, syncOffset, workOffset, polyOffset, iterations, i]); 22 | } 23 | -------------------------------------------------------------------------------- /bench/synchronic-polyfill.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* A polyfill for the synchronic proposal, using futexes and 6 | * spinloops. This should perform reasonably well across platforms 7 | * but is probably suboptimal on all platforms. 8 | * 9 | * See Synchronic.setup() and Synchronic.polyfill() for usage info. 10 | * 11 | * Tested with Chrome 50 Canary, Firefox 47 Nightly, and Firefox 46 12 | * Developer Edition on 2016-02-22. 13 | */ 14 | 15 | var Synchronic = { 16 | 17 | // Number of shared bookkeeping locations, see setup(). 18 | 19 | NUMLOCS: 1, 20 | 21 | // Every worker must call Synchronic.setup() first. It is called 22 | // from polyfillSynchronic, if you use that. "locs" is an 23 | // Int32Array on shared memory that the Synchronic system can use 24 | // for bookkeeping. The length is at least Synchronic.NUMLOCS, 25 | // slots 0 through NUMLOCS will be clobbered by Synchronic. All 26 | // the loc values must be zero before any worker calls setup(). 27 | 28 | setup: function (locs) { 29 | Synchronic._private = locs; 30 | }, 31 | 32 | mustPolyfill: function () { 33 | return !Atomics.expect; 34 | }, 35 | 36 | // locs is an array of shared int32 values, see setup() above. 37 | 38 | polyfill: function(locs) { 39 | if (!(locs instanceof Int32Array) || !(locs.buffer instanceof SharedArrayBuffer) || locs.length < Synchronic.NUMLOCS) 40 | throw new Error("Bad bookkeeping data structure for polyfillSynchronic()"); 41 | Atomics.expect = Synchronic.expect; 42 | Atomics.expectUpdate = Synchronic.expectUpdate; 43 | Atomics.storeNotify = Synchronic.storeNotify; 44 | Atomics.notify = Synchronic.notify; 45 | Synchronic.setup(locs); 46 | }, 47 | 48 | // Wait until i32a[loc] == desired. 49 | 50 | expect: function (i32a, loc, desired) { 51 | for ( var i=Synchronic._spincount ; i >= 0 ; i-- ) 52 | if (Atomics.load(i32a, loc) == desired) 53 | return; 54 | var m = 0; 55 | Atomics.add(Synchronic._private, 0, 1); 56 | while ((m = Atomics.load(i32a, loc)) != desired) 57 | Atomics.wait(i32a, loc, m); 58 | Atomics.sub(Synchronic._private, 0, 1); 59 | }, 60 | 61 | // Wait until i32a[loc] != current or we time out. 62 | 63 | expectUpdate: function (i32a, loc, current, timeout) { 64 | // Bug: the timeout, if present, should be accounted for in 65 | // the spin loop, and should be subtracted from the timeout 66 | // passed to wait(). 67 | for ( var i=Synchronic._spincount ; i >= 0 ; i-- ) 68 | if (Atomics.load(i32a, loc) != current) 69 | return; 70 | Atomics.add(Synchronic._private, 0, 1); 71 | for (;;) { 72 | if (Atomics.wait(i32a, loc, current, timeout) != "ok") 73 | break; 74 | if (Atomics.load(i32a, loc) != current) 75 | break; 76 | } 77 | Atomics.sub(Synchronic._private, 0, 1); 78 | }, 79 | 80 | // Store v in i32a[loc] and notify waiters to re-check conditions. 81 | // 82 | // This can in principle be optimized by only notifying those 83 | // waiters that might respond to the value "v", eg, anyone 84 | // expecing a different value "w" need not be woken. 85 | 86 | storeNotify: function (i32a, loc, v, justOne) { 87 | Atomics.store(i32a, loc, v); 88 | if (Atomics.load(Synchronic._private, 0)) { 89 | // INT_MAX becuse Chrome Canary 50 crashes on Number.POSITIVE_INFINITY 90 | Atomics.wake(i32a, loc, justOne ? 1 : 0x7FFFFFFF); 91 | } 92 | }, 93 | 94 | // Notify waiters to re-check conditions. 95 | 96 | notify: function (i32a, loc, justOne) { 97 | if (Atomics.load(Synchronic._private, 0)) { 98 | // INT_MAX because Chrome Canary 50 crashes on Number.POSITIVE_INFINITY 99 | Atomics.wake(i32a, loc, justOne ? 1 : 0x7FFFFFFF); 100 | } 101 | }, 102 | 103 | // ------------------------------------------------------------ 104 | 105 | _private: null, 106 | 107 | // Value based on minimal experimentation on one (fast) platform: 108 | // This is long enough to allow the "work" between a receive and a 109 | // send in the synchronic benchmark to be doubly-recursive fib(10) 110 | // in Firefox Nightly without going into wait() most of the 111 | // time. (The cutoff for Chrome Canary is below 50 [sic].) See 112 | // synchronic-worker.js. 113 | 114 | _spincount: 5000, 115 | } 116 | -------------------------------------------------------------------------------- /bench/synchronic-worker.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | importScripts("worker-common.js", "synchronic-polyfill.js"); 6 | 7 | var smallWork = true; 8 | 9 | onmessage = 10 | function (ev) { 11 | var [sab, syncOffset, workOffset, polyOffset, iterations, who] = ev.data; 12 | 13 | var sync = new Int32Array(sab, syncOffset, 1); 14 | var work = new Int32Array(sab, workOffset, 1); 15 | 16 | if (Synchronic.mustPolyfill()) 17 | Synchronic.polyfill(new Int32Array(sab, polyOffset, Synchronic.NUMLOCS)); 18 | 19 | var start = Date.now(); 20 | 21 | msg("Worker " + who + " ready"); 22 | var x = 0; 23 | for ( var i=0 ; i < iterations ; i++ ) { 24 | if (who == 0) { 25 | Atomics.expectUpdate(sync, 0, x++); 26 | work[0]+=doWork(); 27 | Atomics.storeNotify(sync, 0, ++x); 28 | } 29 | else { 30 | work[0]+=doWork(); 31 | Atomics.storeNotify(sync, 0, ++x); 32 | Atomics.expectUpdate(sync, 0, x++); 33 | } 34 | } 35 | msg("Worker " + who + " done"); 36 | 37 | if (who == 1) { 38 | assertEqual(2*iterations, sync[0]); 39 | if (smallWork) 40 | assertEqual(2*iterations, work[0]); 41 | msg(Math.round(1000 * (2*iterations) / (Date.now() - start)) + " messages/s"); 42 | } 43 | 44 | msg("Worker " + who + " exiting"); 45 | }; 46 | 47 | var oldWork = 0; 48 | 49 | var doWork = 50 | (smallWork ? 51 | 52 | // Small work - best case, more or less 53 | function() { return 1; } : 54 | // Big work - not an unreasonable amount for a critical section 55 | 56 | function() { return (oldWork = oldWork + fib(10)) & 15; }); 57 | 58 | // Trying to make it not obvious what's going on. 59 | 60 | function fib(n) { 61 | if (n < 2) 62 | return n; 63 | return fibx(n-1) + fiby(n-2); 64 | } 65 | 66 | function fibx(n) { 67 | if (n < 2) 68 | return n; 69 | return fiby(n-1) + fibx(n-2); 70 | } 71 | 72 | function fiby(n) { 73 | if (n < 2) 74 | return n; 75 | return fib(n-1) + fib(n-2); 76 | } 77 | -------------------------------------------------------------------------------- /bench/synchronic.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /bench/uncontended-lock1-worker.js: -------------------------------------------------------------------------------- 1 | importScripts("../util/shim.js", "lock.js"); 2 | 3 | onmessage = function (ev) { 4 | let [ia, lockOffs, countOffs, iterations] = ev.data; 5 | let lock = new Lock(ia, lockOffs); 6 | let then = Date.now(); 7 | for ( let i=0 ; i < iterations ; i++ ) { 8 | lock.lock(); 9 | 10 | // "Small" workload 11 | //ia[countOffs]++; 12 | 13 | // "Medium" workload 14 | ia[countOffs]+=g(h(i)); 15 | 16 | lock.unlock(); 17 | } 18 | let now = Date.now(); 19 | postMessage("Time to execute: " + (now - then) + "ms"); 20 | } 21 | 22 | function g(x) { 23 | return (x ^ 1) + 1; 24 | } 25 | 26 | function h(x) { 27 | return (x & 1) | 1; 28 | } 29 | -------------------------------------------------------------------------------- /bench/uncontended-lock1.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |This test creates a single worker that repeatedly enters and 12 | leaves an uncontended critical region.
13 | 14 | 15 | 16 | 17 | 18 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /bench/uncontended-lock4.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |This test creates four parallel workers that repeatedly enter and 12 | leave separate uncontended critical regions.
13 | 14 | 15 | 16 | 17 | 18 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /bench/worker-common.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | importScripts("../util/shim.js"); 6 | 7 | function msg(s) { 8 | postMessage(s); 9 | } 10 | 11 | function assertEqual(expect, got) { 12 | // Too simple 13 | if (expect !== got) 14 | throw new Error("Error: Expected " + expect + " but got " + got); 15 | } 16 | -------------------------------------------------------------------------------- /bleeding-edge/condtest-master.js: -------------------------------------------------------------------------------- 1 | var sab = new SharedArrayBuffer(4096); 2 | var ia = new Int32Array(sab); 3 | 4 | var numAgents = 4; // even 5 | 6 | // Memory: 7 | // agent 8 | // ... 9 | // lock 10 | // datum 11 | 12 | var BUFSIZ = 64; 13 | var mem_agents = 1; 14 | var mem_lock = mem_agents + (numAgents + 1) * Agent_INTS; 15 | var mem_cond1 = mem_lock + Lock_INTS; 16 | var mem_cond2 = mem_cond1 + Cond_INTS; 17 | var mem_head = mem_cond2 + Cond_INTS; // Index into mem_buffer 18 | var mem_tail = mem_head + 1; // Ditto 19 | var mem_buffer = mem_tail + 1; 20 | var mem_datum = mem_buffer + BUFSIZ; 21 | 22 | Agent.setup(ia, mem_agents, 1); // Master agent is agent #1 in slot 0 23 | Lock.initialize(ia, mem_lock); 24 | Cond.initialize(ia, mem_cond1); 25 | Cond.initialize(ia, mem_cond2); 26 | 27 | // The test: agent 1 pumps data into a queue, the other agents extract 28 | // data from the queue almost as fast as they can. There is a lock on 29 | // the queue data, and two condition variables (spaceAvail, 30 | // dataAvail). The two condition variables use the same lock. 31 | // 32 | // The producer can either use notifyOne after each insertion or it 33 | // can use notifyAll when it goes from empty to nonempty... 34 | 35 | for ( var i=0 ; i < numAgents ; i++ ) { 36 | let w = new Worker("condtest-worker.js"); 37 | w.postMessage([sab, mem_agents + (i+1)*Agent_INTS, i+2, mem_lock, mem_cond1, mem_cond2, mem_head, mem_tail, mem_buffer, BUFSIZ, mem_datum]); 38 | w.onmessage = workerDone; 39 | } 40 | 41 | var done = 0; 42 | 43 | function workerDone() { 44 | //console.log("Worker is done"); 45 | if (++done == numAgents) { 46 | for ( var i=0 ; i < numAgents / 2 ; i++ ) 47 | console.log("Result " + i + ": " + ia[mem_datum + i].toString(16)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bleeding-edge/condtest-worker.js: -------------------------------------------------------------------------------- 1 | importScripts("lock.js"); 2 | 3 | onmessage = function (ev) { 4 | var [sab, mem_agent, id, mem_lock, mem_cond1, mem_cond2, mem_datum] = ev.data; 5 | var ia = new Int32Array(sab); 6 | Agent.setup(ia, mem_agent, id); 7 | var loc = mem_datum + Math.floor((id - 2) / 2); 8 | compute(new Lock(mem_lock), ia, loc, 1 << (16 * ((id-2) % 2))); 9 | } 10 | 11 | //var limit = 64*1024; 12 | var limit = 16*1024-1; 13 | //var limit = 10; 14 | 15 | function compute(lock, ia, offs, k) { 16 | for ( var i=0 ; i < limit ; i++ ) { 17 | lock.lock(); 18 | ia[offs] += k; 19 | lock.unlock(); 20 | } 21 | postMessage("Worker done"); 22 | } 23 | -------------------------------------------------------------------------------- /bleeding-edge/condtest.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /bleeding-edge/futextest.js: -------------------------------------------------------------------------------- 1 | // SpiderMonkey shell test for polyfilled futexes 2 | 3 | load("futex.js"); 4 | 5 | var nelem = 256; 6 | var fsab = new SharedArrayBuffer(Futex.BYTE_SIZE + nelem * 4); 7 | 8 | Futex.initMemory(fsab, 0); 9 | 10 | Futex.setup(fsab, 0); 11 | Futex.tagBuffer(fsab, 0); 12 | 13 | setSharedArrayBuffer(fsab); 14 | 15 | var mymem = new Int32Array(fsab, Futex.BYTE_SIZE, nelem); 16 | 17 | evalInWorker(` 18 | load("futex.js"); 19 | var fsab = getSharedArrayBuffer(); 20 | 21 | Futex.setup(fsab, 0); 22 | Futex.tagBuffer(fsab, 0); 23 | 24 | var mymem = new Int32Array(fsab, Futex.BYTE_SIZE, ${nelem}); 25 | 26 | var then = Date.now(); 27 | Futex.wait(mymem, 0, 0); 28 | var now = Date.now(); 29 | console.log("Waited (should be approx 1000): " + (now - then)); 30 | 31 | var then = Date.now(); 32 | Futex.wait(mymem, 0, 0, 500); 33 | var now = Date.now(); 34 | console.log("Waited (should be approx 500): " + (now - then)); 35 | `); 36 | 37 | sleep(1); 38 | Futex.wake(mymem, 0, 1); 39 | 40 | sleep(1); 41 | Futex.wake(mymem, 0, 1); 42 | -------------------------------------------------------------------------------- /bleeding-edge/locktest-master.js: -------------------------------------------------------------------------------- 1 | var sab = new SharedArrayBuffer(4096); 2 | var ia = new Int32Array(sab); 3 | 4 | var numAgents = 32; // even 5 | 6 | // Memory: 7 | // agent 8 | // ... 9 | // lock 10 | // datum 11 | 12 | var mem_agents = 1; 13 | var mem_lock = mem_agents + (numAgents + 1) * Agent_INTS; 14 | var mem_datum = mem_lock + Lock_INTS; 15 | 16 | Agent.setup(ia, mem_agents, 1); // Master agent is agent #1 in slot 0 17 | Lock.initialize(ia, mem_lock); 18 | 19 | for ( var i=0 ; i < numAgents ; i++ ) { 20 | let w = new Worker("locktest-worker.js"); 21 | w.postMessage([sab, mem_agents + (i+1)*Agent_INTS, i+2, mem_lock, mem_datum]); 22 | w.onmessage = workerDone; 23 | } 24 | 25 | var done = 0; 26 | 27 | function workerDone() { 28 | //console.log("Worker is done"); 29 | if (++done == numAgents) { 30 | for ( var i=0 ; i < numAgents / 2 ; i++ ) 31 | console.log("Result " + i + ": " + ia[mem_datum + i].toString(16)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bleeding-edge/locktest-worker.js: -------------------------------------------------------------------------------- 1 | importScripts("lock.js"); 2 | 3 | onmessage = function (ev) { 4 | var [sab, mem_agent, id, mem_lock, mem_datum] = ev.data; 5 | var ia = new Int32Array(sab); 6 | Agent.setup(ia, mem_agent, id); 7 | var loc = mem_datum + Math.floor((id - 2) / 2); 8 | compute(new Lock(mem_lock), ia, loc, 1 << (16 * ((id-2) % 2))); 9 | } 10 | 11 | //var limit = 64*1024; 12 | var limit = 16*1024-1; 13 | //var limit = 10; 14 | 15 | function compute(lock, ia, offs, k) { 16 | for ( var i=0 ; i < limit ; i++ ) { 17 | lock.lock(); 18 | ia[offs] += k; 19 | lock.unlock(); 20 | } 21 | postMessage("Worker done"); 22 | } 23 | -------------------------------------------------------------------------------- /bleeding-edge/locktest.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | Demo programs showcasing the various features of the library. 2 | 3 | * mandelbrot-animation is a Mandelbrot set animation built on the asymmetric-barrier. 4 | * mandelbrot-animation2 is a Mandelbrot set animation built on the Par framework. It gets better performance than the simple program by automatically tiling the computation, and by overlapping computation and display. 5 | * mandelbrot-animation-simd [OBSOLETE] is the same as mandelbrot-animation2 but using asm.js for the kernel and optionally SIMD to compute multiple pixels at a time. asm.js by itself does not improve the performance over straight Javascript, but simd boosts the performance by a factor of 1.8. However, since it uses float and not double it has lower quality at high magnification levels. [Obsolete because shared memory and SIMD are no longer available in asm.js in Firefox] 6 | * renderWorld is a benchmark program authored by Intel (originally for Parallel Javascript testing), computing and displaying a flight over a Minecraft-like landscape. It can be run both on the main thread and in workers, using the Par framework. 7 | -------------------------------------------------------------------------------- /demo/mandelbrot-animation-simd/mandelbrot-master.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | "use strict"; 6 | 7 | // Animation parameters 8 | 9 | const magFactor = 1.025; // with 1.5 pixelation is a problem after a while, because of float32 10 | const maxIterations = 400; 11 | 12 | // The memory contains two height*width grids (so that we can overlap 13 | // display and computation) and extra shared space for the Par 14 | // framework. 15 | 16 | const rawmem = new SharedArrayBuffer(roundupAsmJSHeapLength(4*(height*width*2 + MasterPar.NUMINTS + 8))); 17 | const mem1 = new Int32Array(rawmem, 0, height*width); 18 | const mem2 = new Int32Array(rawmem, height*width*4, height*width); 19 | const memp = new Int32Array(rawmem, height*width*4*2, MasterPar.NUMINTS); 20 | const colbase = 4*(height*width*2 + MasterPar.NUMINTS); 21 | const colmem = new Int32Array(rawmem, colbase, 8); 22 | 23 | // Note, numWorkers is set by the .html document. 24 | 25 | const Par = new MasterPar(memp, 0, numWorkers, "mandelbrot-worker.js", setupMandelbrot); 26 | 27 | var magnification = 1; 28 | var going_in = true; 29 | var iterations = 0; 30 | var fps_iterations = 0; 31 | var mem = mem1; 32 | var timeBefore; 33 | var lastDisplay = 0; 34 | 35 | function setupMandelbrot() { 36 | Par.broadcast(doMandelbrot, "setup_asm", colmem); 37 | } 38 | 39 | function doMandelbrot() { 40 | Par.invoke(showMandelbrot, "mandelbrot_" + mode, [[0,height], [0,width]], mem, magnification); 41 | } 42 | 43 | function setMode(new_mode) { 44 | mode = new_mode; 45 | fps_iterations = 0; 46 | if (iterations > 0) 47 | timeBefore = Date.now(); 48 | } 49 | 50 | function showMandelbrot() { 51 | var doDisplay = true; 52 | var memnow = mem; 53 | var now = Date.now(); 54 | 55 | if (iterations == 0) { 56 | timeBefore = now; 57 | lastDisplay = now; 58 | doDisplay = false; 59 | } 60 | 61 | iterations++; 62 | fps_iterations++; 63 | if (iterations == maxIterations) { 64 | going_in = !going_in; 65 | iterations = 1; 66 | } 67 | 68 | if (going_in) 69 | magnification *= magFactor; 70 | else 71 | magnification /= magFactor; 72 | 73 | mem = (memnow == mem1) ? mem2 : mem1; 74 | 75 | // Overlap display of this frame with computation of the next. 76 | doMandelbrot(); 77 | 78 | if (now - lastDisplay >= 1000) { 79 | lastDisplay = now; 80 | var t = now - timeBefore; 81 | var fps = Math.round((fps_iterations/(t/1000))*10)/10; 82 | document.getElementById('mystatus').innerHTML = 83 | "Mode: " + mode + " " + 84 | "Number of workers: " + numWorkers + " Compute time: " + t + "ms FPS=" + fps; 85 | } 86 | 87 | if (doDisplay) { 88 | canvasSetFromABGRBytes(document.getElementById("mycanvas"), 89 | new Uint8Array(rawmem, memnow.byteOffset, height*width*4), 90 | height, 91 | width); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /demo/mandelbrot-animation-simd/mandelbrot-parameters.js: -------------------------------------------------------------------------------- 1 | // Center the image at this location. 2 | const g_center_x = -0.743643887037158704752191506114774; 3 | const g_center_y = 0.131825904205311970493132056385139; 4 | 5 | // Pixel grid. (0,0) correspons to (bottom,left) 6 | const height = 480; 7 | const width = 640; 8 | 9 | // Max iterations. This is referenced as a property on the global 10 | // object, so can't be "const". 11 | var MAXIT = 200; 12 | 13 | // The Intel Mandelbrot SIMD demo uses these: 14 | // const height = 400; 15 | // const width = 600; 16 | // const MAXIT = 100; 17 | -------------------------------------------------------------------------------- /demo/mandelbrot-animation-simd/mandelbrot.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |FPS
54 | 55 | 56 | Overlap display and computation 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /doc/TUTORIAL.md: -------------------------------------------------------------------------------- 1 | # Using parlib-simple 2 | 3 | ## Symmetric and asymmetric data types 4 | 5 | *Symmetric* data types are those that do not distinguish between the 6 | agents participating. For example, `Barrier` just knows that a number 7 | of agents will enter and once they have they will be released. 8 | 9 | *Asymmetric* data types distinguish between the *Master* and the 10 | *Workers* (and where the master is almost always the window's main 11 | thread). For example, `MasterBarrier` and `WorkerBarrier` comprise 12 | an asymmetric barrier where the master gets a callback when the 13 | workers are all in the barrier. 14 | 15 | The purpose of asymmetric data types is to enable computations that 16 | don't block the window's main thread. 17 | 18 | 19 | ## Allocating and initializing shared memory 20 | 21 | All the data types represent JS objects that are completely local to a 22 | single realm (the main thread or a worker). However, the data types 23 | all need a little private storage in shared memory, shared among 24 | several instances of the data type. 25 | 26 | For constructors that take a SharedInt32Array as the first parameter 27 | the amount of shared storage is published on the data type as the 28 | `NUMINTS` property, eg, `Lock.NUMINTS`. The second parameter to the 29 | constructor is then invariably the index of the first location in the 30 | integer array that is reserved for that object. The program must 31 | manage that memory explicitly, though it can use a utility allocator 32 | class for that (see below). 33 | 34 | Symmetric and asymmetric data structures initialize that shared 35 | storage differently. 36 | 37 | The symmetric data structures have a static `initialize()` method that 38 | must be called on the memory once, before any objects are constructed 39 | on the memory. All agents use the same constructor. 40 | 41 | The asymmetric data structures instead have two constructors, one for 42 | the master side and one for the worker side, and the master 43 | constructor initializes the shared memory (and must always return 44 | before any worker constructors are called). 45 | 46 | 47 | ## The allocator 48 | 49 | There is a data type, `BumpAlloc`, that provides simple memory 50 | allocation services. It is initialized with a SharedArrayBuffer and 51 | can be used to allocate ranges of words in that buffer. It works 52 | across multiple agents and is thread-safe. 53 | -------------------------------------------------------------------------------- /src/arena.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* 6 | * A very simple allocator for bump-allocation within a SharedArrayBuffer 7 | * or ArrayBuffer, with integrated alignment management. 8 | */ 9 | 10 | "use strict"; 11 | 12 | /* 13 | * Construct the arena. 14 | * 15 | * ab must be an ArrayBuffer or SharedArrayBuffer. 16 | * offset must be a valid index within ab. 17 | * length must be a nonnegative integer. 18 | * offset + length - 1 must be a valid index within ab. 19 | */ 20 | function ArrayBufferArena(ab, offset, length) { 21 | if (!((ab instanceof SharedArrayBuffer || ab instanceof ArrayBuffer) && 22 | (offset|0) === offset && 23 | offset >= 0 && offset < ab.byteLength && 24 | (length|0) === length && 25 | length >= 0 && offset + length <= ab.byteLength)) 26 | { 27 | throw new Error("Bad arena parameters: " + ab + " " + offset + " " + length); 28 | } 29 | this._ab = ab; 30 | this._offset = offset; 31 | this._limit = offset + length; 32 | this._initMem = null; 33 | } 34 | 35 | /* 36 | * Returns the underlying buffer. 37 | */ 38 | Object.defineProperty(ArrayBufferArena.prototype, 39 | "buffer", 40 | { get: function () { return this._ab } }); 41 | 42 | /* 43 | * Allocate nbytes, aligned on "align" bytes (not optional) within the buffer. 44 | * If "initialize" is true then zero-fill the memory. 45 | * Returns the offset within the buffer of the newly allocated area. 46 | * Throws an Error on heap overflow. 47 | */ 48 | ArrayBufferArena.prototype.alloc = function (nbytes, align, initialize) { 49 | var p = this._alignPtr(align); 50 | if (p + nbytes <= this._limit) { 51 | this._offset = p + nbytes; 52 | if (initialize) { 53 | var mem = this._getInitMem(); 54 | for ( var i=p, limit=p+nbytes ; i < limit ; i++ ) 55 | mem[i] = 0; 56 | } 57 | return p; 58 | } 59 | throw new Error("ArrayBufferArena exhausted"); 60 | } 61 | 62 | /* 63 | * Compute the amount of space that would be available to an allocation 64 | * aligned on "align" bytes (not optional). 65 | * Returns that amount. 66 | */ 67 | ArrayBufferArena.prototype.available = function (align) { 68 | var p = this._alignPtr(align); 69 | return Math.max(this._limit - p, 0); 70 | } 71 | 72 | /* 73 | * Fill the memory of the buffer in the range [p, p+len) with bytes of 74 | * value val and optionally perform a synchronizing operation at the end 75 | * (synchronization is only valid if the buffer is a SharedArrayBuffer). 76 | * Throws an error on out-of-bounds accesses. 77 | */ 78 | ArrayBufferArena.prototype.memset = function (p, val, len, synchronize) { 79 | if (len == 0) 80 | return; 81 | var mem = this._getInitMem(); 82 | if (!((p|0) === p && p >= 0 && p <= mem.length && (len|0) === len && len >= 0 && p + len <= mem.length)) 83 | throw new Error("Bad memory range: " + p + " " + len); 84 | for ( var i=p, limit=p+len ; i < limit ; i++ ) 85 | mem[i] = val; 86 | if (synchronize && this._ab instanceof SharedArrayBuffer) 87 | Atomics.store(mem, p, val); 88 | } 89 | 90 | // Internal methods beyond this point. 91 | 92 | ArrayBufferArena.prototype._alignPtr = function (align) { 93 | if ((align|0) !== align || align < 0) 94 | throw new Error("Bad alignment: " + align) 95 | return Math.floor((this._offset + (align - 1)) / align) * align; 96 | } 97 | 98 | ArrayBufferArena.prototype._getInitMem = function () { 99 | if (!this._initMem) { 100 | var ab = this._ab; 101 | var offset = this._offset; 102 | var length = this._limit - offset; 103 | this._initMem = new Int8Array(ab, 0, offset+length); 104 | } 105 | return this._initMem; 106 | } 107 | -------------------------------------------------------------------------------- /src/asymmetric-barrier.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // REQUIRE: 6 | // message.js 7 | 8 | // Asymmetric barrier synchronization. 9 | // 2015-01-19 / v1 10 | // 2016-03-07 / v2, handle message events differently, see addWorker() 11 | 12 | // MasterBarrier / WorkerBarrier. 13 | // 14 | // This is a simple master/worker barrier that is mapped to locations 15 | // within a shared integer array. 16 | // 17 | // The purpose of this barrier is to allow the master--the main thread 18 | // of the window--not to block, but to receive a callback when the 19 | // workers have all entered the barrier. 20 | // 21 | // Overview 22 | // -------- 23 | // The master and workers all create private barrier objects, which 24 | // reference the same working locations in shared memory. When the 25 | // workers have all entered the barrier the master receives a 26 | // callback. The master must then release the workers again for them 27 | // to resume computing. 28 | // 29 | // Usage 30 | // ----- 31 | // Client code in the master must create a MasterBarrier object per 32 | // barrier. 33 | // 34 | // The workers must each create a WorkerBarrier on the same shared 35 | // locations and with the same ID as the master barrier. The 36 | // WorkerBarriers must not be created until after the MasterBarrier 37 | // constructor has returned. 38 | // 39 | // Client code in the master must call MasterBarrier.addWorker(w) on 40 | // each worker w to install event handling machinery in that worker. 41 | // 42 | // The application is responsible for allocating the locations in the 43 | // integer array and communicating those and the ID to the workers. 44 | // 45 | // The number of consecutive int32 array locations needed is given by 46 | // MasterBarrier.NUMINTS. 47 | 48 | "use strict"; 49 | 50 | // Create the master side of a barrier. 51 | // 52 | // - 'iab' is an Int32Array on shared memory. 53 | // - 'ibase' is the first of MasterBarrier.NUMINTS consecutive locations 54 | // within 'iab' 55 | // - 'ID' identifies the barrier globally 56 | // - 'numWorkers' is the number of workers that will coordinate 57 | // - 'callback' is the function that is to be called when the workers 58 | // are all waiting in the barrier with this ID. 59 | // 60 | // 'iab', 'ibase', 'ID', and 'numWorkers' are exposed on the object. 61 | 62 | function MasterBarrier(iab, ibase, ID, numWorkers, callback) { 63 | this.iab = iab; 64 | this.ibase = ibase; 65 | this.numWorkers = numWorkers; 66 | this.ID = ID; 67 | 68 | const counterLoc = ibase; 69 | const seqLoc = ibase+1; 70 | 71 | iab[counterLoc] = numWorkers; 72 | iab[seqLoc] = 0; 73 | MasterBarrier._callbacks[ID] = callback; 74 | } 75 | 76 | // Call this with any worker w that participates in any barrier to 77 | // install message handling machinery for the barrier. One invocation 78 | // per worker is enough. 79 | 80 | MasterBarrier.addWorker = function (w) { 81 | dispatchMessage(w, "MasterBarrier.dispatch", function (data) { 82 | const id = data[1]; 83 | const cb = MasterBarrier._callbacks[id]; 84 | if (!cb) 85 | throw new Error("Unknown barrier ID: " + id); 86 | return cb(); 87 | }); 88 | } 89 | 90 | // PRIVATE. Maps barrier IDs to callback functions. 91 | 92 | MasterBarrier._callbacks = {}; 93 | 94 | // The number of consecutive locations in the integer array needed for 95 | // the barrier. 96 | 97 | MasterBarrier.NUMINTS = 2; 98 | 99 | // Return true iff the workers are all waiting in the barrier. 100 | // 101 | // Note that this is racy; if the result is false the workers may all 102 | // in fact be waiting because the last worker could have entered after 103 | // the check was performed but before isQuiescent() returned. 104 | 105 | MasterBarrier.prototype.isQuiescent = 106 | function () { 107 | const iab = this.iab; 108 | const counterLoc = this.ibase; 109 | 110 | return Atomics.load(iab, counterLoc) == 0; 111 | }; 112 | 113 | // If the workers are not all waiting in the barrier then return false. 114 | // Otherwise release them and return true. 115 | // 116 | // Note that if the result is false the workers may all in fact be 117 | // waiting because the last worker could have entered after the check 118 | // was performed but before isQuiescent() returned. 119 | 120 | // The barrier is immediately reusable after the workers are released. 121 | 122 | MasterBarrier.prototype.release = 123 | function () { 124 | if (!this.isQuiescent()) 125 | return false; 126 | 127 | const iab = this.iab; 128 | const counterLoc = this.ibase; 129 | const seqLoc = counterLoc+1; 130 | const numWorkers = this.numWorkers; 131 | 132 | Atomics.store(iab, counterLoc, numWorkers); 133 | Atomics.add(iab, seqLoc, 1); 134 | Atomics.wake(iab, seqLoc, numWorkers); 135 | Atomics.add(iab, seqLoc, 1); 136 | return true; 137 | }; 138 | 139 | // Create the worker side of a barrier. 140 | // 141 | // - 'iab' is an Int32Array on shared memory 142 | // - 'ibase' is the first of several consecutive locations within 'iab' 143 | // - 'ID' identifies the barrier globally 144 | // 145 | // 'iab', 'ibase', and 'ID' are all exposed on the object. 146 | 147 | function WorkerBarrier(iab, ibase, ID) { 148 | this.iab = iab; 149 | this.ibase = ibase; 150 | this.ID = ID; 151 | } 152 | 153 | // Enter the barrier. This call will block until the master has 154 | // released all the workers. 155 | 156 | WorkerBarrier.prototype.enter = 157 | function () { 158 | const iab = this.iab; 159 | const counterLoc = this.ibase; 160 | const seqLoc = counterLoc+1; 161 | const ID = this.ID; 162 | 163 | const seq = Atomics.load(iab, seqLoc); 164 | if (Atomics.sub(iab, counterLoc, 1) == 1) 165 | postMessage(["MasterBarrier.dispatch", ID]); 166 | Atomics.wait(iab, seqLoc, seq, Number.POSITIVE_INFINITY); 167 | while (Atomics.load(iab, seqLoc) & 1) 168 | ; 169 | }; 170 | -------------------------------------------------------------------------------- /src/barrier.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // A simple barrier sync. 6 | 7 | "use strict"; 8 | 9 | ////////////////////////////////////////////////////////////////////// 10 | // 11 | // Barriers. 12 | // 13 | // Barriers are JS objects that use some shared memory for private 14 | // data. The number of shared int32 locations needed is given by 15 | // Barrier.NUMINTS. The shared memory for a barrier should be 16 | // initialized once by calling Barrier.initialize() on the memory, 17 | // before constructing the first Barrier object in any agent. 18 | 19 | // Implementation note: This barrier predates the Synchronic facility 20 | // and is therefore implemented on top of bare atomics and futexes. 21 | // It could equally well be implemented on top of Synchronic. 22 | 23 | // Create a barrier object. 24 | // 25 | // 'iab' is an Int32Array on shared memory. 26 | // 'ibase' is the first of Barrier.NUMINTS slots within iab reserved 27 | // for the barrier. 28 | // 29 | // iab and ibase will be exposed on the Barrier. 30 | function Barrier(iab, ibase) { 31 | if (!(iab instanceof Int32Array && ibase|0 == ibase && ibase >= 0 && ibase+Barrier.NUMINTS <= iab.length)) 32 | throw new Error("Bad arguments to Barrier constructor: " + iab + " " + ibase); 33 | this.iab = iab; 34 | this.ibase = ibase; 35 | } 36 | 37 | // Number of shared Int32 locations needed by the barrier. 38 | Barrier.NUMINTS = 3; 39 | 40 | // Initialize the shared memory for a barrier. 41 | // 42 | // 'iab' is an Int32Array on shared memory. 43 | // 'ibase' is the first of Barrier.NUMINTS slots within iab reserved 44 | // for the barrier. 45 | // 'numAgents' is the number of participants in the barrier. 46 | // 47 | // Returns 'ibase'. 48 | Barrier.initialize = 49 | function (iab, ibase, numAgents) { 50 | if (!(iab instanceof Int32Array && 51 | ibase|0 == ibase && 52 | ibase >= 0 && 53 | ibase+Barrier.NUMINTS <= iab.length && 54 | numAgents|0 == numAgents)) 55 | { 56 | throw new Error("Bad arguments to Barrier initializer: " + iab + " " + ibase + " " + numAgents); 57 | } 58 | 59 | const counterLoc = ibase; 60 | const seqLoc = ibase+1; 61 | const numAgentsLoc = ibase+2; 62 | 63 | Atomics.store(iab, counterLoc, numAgents); 64 | Atomics.store(iab, seqLoc, 0); 65 | Atomics.store(iab, numAgentsLoc, numAgents); 66 | 67 | return ibase; 68 | }; 69 | 70 | // Enter the barrier. This will block until all agents have entered 71 | // the barrier, at which point all agents are automatically released. 72 | // The barrier is then immediately usable. 73 | Barrier.prototype.enter = 74 | function () { 75 | const iab = this.iab; 76 | const ibase = this.ibase; 77 | 78 | const counterLoc = ibase; 79 | const seqLoc = ibase+1; 80 | const numAgentsLoc = ibase+2; 81 | 82 | // The sequence number must be read before the check, otherwise 83 | // when we wait there is the possibility that the waiting thread 84 | // will read the sequence number that has been updated by the 85 | // non-waiting thread, and incorrectly wait on that. 86 | const seq = Atomics.load(iab, seqLoc); 87 | if (Atomics.sub(iab, counterLoc, 1) == 1) { 88 | const numAgents = iab[numAgentsLoc]; 89 | iab[counterLoc] = numAgents; 90 | Atomics.add(iab, seqLoc, 1); 91 | Atomics.wake(iab, seqLoc, numAgents-1); 92 | Atomics.add(iab, seqLoc, 1); 93 | } 94 | else { 95 | Atomics.wait(iab, seqLoc, seq, Number.POSITIVE_INFINITY); 96 | // Wait until the master is done waking all threads 97 | while (Atomics.load(iab, seqLoc) & 1) 98 | ; 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /src/buffer.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* 6 | * Simple multi-producer multi-consumer bounded buffer. 7 | * 8 | * This is built on the locks and condition variables in lock.js and 9 | * is fairly limited, but supports any primitive type of element (eg, 10 | * it can be constructed on top of a Float64Array). 11 | * 12 | * For an alternative approach that supports bundles of integer items 13 | * and that also allows for timeouts, and which is built on 14 | * Synchronic, see intqueue.js. 15 | */ 16 | 17 | // REQUIRE 18 | // lock.js 19 | 20 | "use strict"; 21 | 22 | ////////////////////////////////////////////////////////////////////// 23 | // 24 | // Bounded buffers. 25 | // 26 | // Bounded buffers are JS objects that use some shared memory for 27 | // private data. The number of shared int32 locations needed is given 28 | // by Buffer.NUMINTS; this is separate from data needed for buffer 29 | // value storage. The shared private memory for a buffer should be 30 | // initialized once by calling Buffer.initialize() on the memory, 31 | // before constructing the first Buffer object in any agent. 32 | // 33 | // Implementation notes: 34 | // - The motivation for tracking the number of waiting consumers and 35 | // producers is to avoid calling cond.wake on every insert. 36 | // - This buffer does not try to be clever about contention or lock 37 | // overhead and so probably doesn't scale to very many CPUs. 38 | 39 | // Create a Buffer object. 40 | // 41 | // 'iab' is a Int32Array on a SharedArrayBuffer, for book-keeping data. 42 | // 'ibase' is the first of Buffer.NUMINTS locations in iab reserved 43 | // for this Buffer. 44 | // 'dab' is a TypedArray on a SharedArrayBuffer, for the buffer data. 45 | // 'dbase' is the first location in 'dab' for buffer data. 46 | // 'dsize' is the number of locations in 'dab' for buffer data. 47 | // 48 | // The five parameters should be the same in all agents. Also, though 49 | // iab and dab will reference different JS objects in different agents 50 | // they should ultimately reference the same underlying shared memory 51 | // at the same buffer offsets. 52 | // 53 | // iab, ibase, dab, dbase, and dsize will be exposed on the Barrier. 54 | 55 | function Buffer(iab, ibase, dab, dbase, dsize) { 56 | // TODO: could also check that dab is any TypedArray and 57 | // that dbase and dsize are in bounds. 58 | if (!(iab instanceof Int32Array && ibase|0 == ibase && ibase >= 0 && ibase+Buffer.NUMINTS <= iab.length)) 59 | throw new Error("Bad arguments to Buffer constructor: " + iab + " " + ibase); 60 | this.iab = iab; 61 | this.ibase = ibase; 62 | this.dab = dab; 63 | this.dbase = dbase; 64 | this.dsize = dsize; 65 | var lockIdx = ibase+5; 66 | var nonemptyIdx = lockIdx + Lock.NUMINTS; 67 | var nonfullIdx = nonemptyIdx + Cond.NUMINTS; 68 | this.lock = new Lock(iab, lockIdx); 69 | this.nonempty = new Cond(this.lock, nonemptyIdx); 70 | this.nonfull = new Cond(this.lock, nonfullIdx); 71 | } 72 | 73 | Buffer.NUMINTS = Lock.NUMINTS + 2*Cond.NUMINTS + 5; 74 | 75 | // Initialize shared memory for a Buffer object (its private memory, 76 | // not the buffer memory proper). 77 | // 78 | // 'iab' is a Int32Array on a SharedArrayBuffer, for book-keeping data. 79 | // 'ibase' is the first of Buffer.NUMINTS locations in iab reserved 80 | // for this Buffer. 81 | // 82 | // Returns 'ibase'. 83 | Buffer.initialize = 84 | function (iab, ibase) { 85 | if (!(iab instanceof Int32Array && ibase|0 == ibase && ibase >= 0 && ibase+Buffer.NUMINTS <= iab.length)) 86 | throw new Error("Bad arguments to Buffer initializer: " + iab + " " + ibase); 87 | const leftIdx = ibase; 88 | const rightIdx = ibase+1; 89 | const availIdx = ibase+2; 90 | const producersWaitingIdx = ibase+3; 91 | const consumersWaitingIdx = ibase+4; 92 | 93 | Atomics.store(iab, leftIdx, 0); 94 | Atomics.store(iab, rightIdx, 0); 95 | Atomics.store(iab, availIdx, 0); 96 | Atomics.store(iab, producersWaitingIdx, 0); 97 | Atomics.store(iab, consumersWaitingIdx, 0); 98 | var lockIdx = ibase+5; 99 | var nonemptyIdx = lockIdx + Lock.NUMINTS; 100 | var nonfullIdx = nonemptyIdx + Cond.NUMINTS; 101 | Lock.initialize(iab, lockIdx); 102 | Cond.initialize(iab, nonemptyIdx); 103 | Cond.initialize(iab, nonfullIdx); 104 | 105 | return ibase; 106 | }; 107 | 108 | // Remove one element, wait until one is available. 109 | Buffer.prototype.take = 110 | function (index) { 111 | const iab = this.iab; 112 | const ibase = this.ibase; 113 | const leftIdx = ibase; 114 | const availIdx = ibase+2; 115 | const producersWaitingIdx = ibase+3; 116 | const consumersWaitingIdx = ibase+4; 117 | 118 | this.lock.lock(); 119 | while (iab[availIdx] == 0) { 120 | iab[consumersWaitingIdx]++; 121 | this.nonempty.wait(); 122 | iab[consumersWaitingIdx]--; 123 | } 124 | var left = iab[leftIdx]; 125 | var value = this.dab[this.dbase+left]; 126 | iab[leftIdx] = (left+1) % this.dsize; 127 | iab[availIdx]--; 128 | if (iab[producersWaitingIdx] > 0) 129 | this.nonfull.wake(); 130 | this.lock.unlock(); 131 | return value; 132 | }; 133 | 134 | // Insert one element, wait until space is available. 135 | Buffer.prototype.put = 136 | function (value) { 137 | const iab = this.iab; 138 | const ibase = this.ibase; 139 | const rightIdx = ibase+1; 140 | const availIdx = ibase+2; 141 | const producersWaitingIdx = ibase+3; 142 | const consumersWaitingIdx = ibase+4; 143 | 144 | this.lock.lock(); 145 | while (iab[availIdx] == this.dsize) { 146 | iab[producersWaitingIdx]++; 147 | this.nonfull.wait(); 148 | iab[producersWaitingIdx]--; 149 | } 150 | var right = iab[rightIdx]; 151 | this.dab[this.dbase+right] = value; 152 | iab[rightIdx] = (right+1) % this.dsize; 153 | iab[availIdx]++; 154 | if (iab[consumersWaitingIdx] > 0) 155 | this.nonempty.wake(); 156 | this.lock.unlock(); 157 | }; 158 | 159 | // Return the number of additional elements that can be inserted into 160 | // the buffer before the insert will block. 161 | Buffer.prototype.remainingCapacity = 162 | function () { 163 | const availIdx = this.ibase+2; 164 | 165 | this.lock.lock(); 166 | var available = this.iab[availIdx]; 167 | this.lock.unlock(); 168 | return this.dsize - available; 169 | }; 170 | 171 | -------------------------------------------------------------------------------- /src/channel.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* 6 | * Simple unidirectional marshaling shared-memory channel. There can 7 | * be multiple senders and multiple receivers. 8 | * 9 | * This is by and large like postMessage() except that it cannot 10 | * transfer ArrayBuffer values (it can only copy them), and it cannot 11 | * send or receive SharedArrayBuffer values at all. Also, the 12 | * marshaler currently does not deal with circular/shared structure 13 | * but that's fixable. 14 | */ 15 | 16 | // REQUIRE: 17 | // arena.js 18 | // marshaler.js 19 | // intqueue.js 20 | // synchronic.js 21 | 22 | "use strict"; 23 | 24 | /* 25 | * Create a sender endpoint of the channel. 26 | * 27 | * "sab" is a SharedArrayBuffer, "offset" is a byte offset within that 28 | * buffer, "length" is the length of the region reserved as a message 29 | * buffer. 30 | * 31 | * All endpoints must be created before either send or receive may be 32 | * called on the channel. All endpoints must be created with the same 33 | * values for sab, offset, and length. The memory must be 34 | * zero-initialized before use and the zero values visible in all 35 | * threads. 36 | * 37 | * How much space will you need? The channel transmits a stream of 38 | * tag+value pairs, or fieldname+tag+value triples in objects. It 39 | * optimizes transmission of typed data structures (strings, 40 | * TypedArrays) by omitting tags when it can. If mostly small data 41 | * structures are being sent then a few kilobytes should be enough to 42 | * allow a number of messages to sit in a queue at once. 43 | */ 44 | function ChannelSender(sab, offset, length) { 45 | this._queue = new IntQueue(sab, offset, length); 46 | this._marshaler = new Marshaler(); 47 | } 48 | 49 | /* 50 | * Send a message on the channel, waiting for up to t milliseconds for 51 | * available space (undefined == indefinite wait), and then return 52 | * without waiting for the recipient to pick up the message. 53 | * 54 | * Returns true if the message was sent, false if space did not become 55 | * available. 56 | * 57 | * Throws ChannelEncodingError on encoding error. 58 | */ 59 | ChannelSender.prototype.send = function(msg, t) { 60 | try { 61 | var {values, newSAB} = this._marshaler.marshal([msg]); 62 | } 63 | catch (e) { 64 | // TODO: This could be improved by making the Marshaler throw useful errors. 65 | throw new ChannelEncodingError("Marshaler failed:\n" + e); 66 | } 67 | if (newSAB.length) 68 | throw new ChannelEncodingError("SharedArrayBuffer not supported"); 69 | return this._queue.enqueue(values, t); 70 | } 71 | 72 | /* 73 | * Create a receiver endpoint. See comments on the sender endpoint. 74 | */ 75 | function ChannelReceiver(sab, offset, length) { 76 | this._queue = new IntQueue(sab, offset, length); 77 | this._marshaler = new Marshaler(); 78 | } 79 | 80 | /* 81 | * Receive a message from the channel, waiting for up to t 82 | * milliseconds (undefined == indefinite wait) until there is a 83 | * message if necessary. Returns the message, or the noMessage value 84 | * if none was received. 85 | */ 86 | ChannelReceiver.prototype.receive = function (t, noMessage) { 87 | var M = this._queue.dequeue(t); 88 | if (M == null) 89 | return noMessage; 90 | return this._marshaler.unmarshal(M, 0, M.length)[0]; 91 | } 92 | 93 | /* 94 | * Error object. 95 | */ 96 | function ChannelEncodingError(message) { 97 | this.message = message; 98 | } 99 | ChannelEncodingError.prototype = new Error; 100 | -------------------------------------------------------------------------------- /src/float64atomics.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Atomic operations on Float64Array on SharedArrayBuffer. 6 | // 7 | // The regular Atomics object does not provide atomic operations on 8 | // Float64Array because not all plausible hardware can provide 8-byte 9 | // atomic operations (ARMv6, MIPS32). 10 | // 11 | // This polyfill provides these operations: 12 | // 13 | // Atomics.float64Load 14 | // Atomics.float64Store 15 | // Atomics.float64CompareExchange 16 | // Atomics.float64Add 17 | // Atomics.float64Sub 18 | // 19 | // and this initialization function: 20 | // 21 | // Atomics.float64Init 22 | // 23 | // If an operation throws then the data word is either updated or not 24 | // updated, and the coordination word is left in a state where it will 25 | // not impede progress of other accessing threads. 26 | // 27 | // API notes: 28 | // 29 | // Once these get implemented natively they will be folded into the 30 | // existing load(), store(), etc methods, and will lose their 31 | // "float64" prefix. Also, float64Init and NUMF64INTS will disappear. 32 | 33 | // Implementation notes: 34 | // 35 | // We use a spinlock since no 64-bit CAS is provided by Atomics. 36 | // There are multi-word CAS algorithms built from more primitive 37 | // operations that claim to be practical (see eg 38 | // http://www.timharris.co.uk/papers/2002-disc.pdf and for a wait-free 39 | // one http://www.mscs.mu.edu/~brylow/SPLASH-MARC-2013/Feldman.pdf); 40 | // such an algorithm would still require three or more data words and 41 | // additionally some bit operations to rearrange the values, since the 42 | // datum would be split across the three words to provide a bit of 43 | // bookkeeping in each. 44 | 45 | if (!Atomics.hasOwnProperty("float64Init")) { 46 | (function () { 47 | // Shared coordination data. 48 | 49 | var iab = null; 50 | var iidx = 0; 51 | 52 | // Temps used for last-ditch equality checking. Could use 53 | // block-scoped "const" when that is available. 54 | 55 | var _f64tmp = new Float64Array(2); 56 | var _i32tmp = new Int32Array(_f64tmp.buffer); 57 | 58 | // float64Init must be called once with a Int32Array and an 59 | // index within that array that represents the start of a 60 | // range of Atomics.NUMF64INTS integers. The shared memory 61 | // locations denoted by those values should be the same in all 62 | // agents, and they must be initialized to zero before the 63 | // first such call is made. 64 | 65 | Atomics.float64Init = function (iab_, iidx_) { 66 | iab = iab_; 67 | iidx = iidx_; 68 | } 69 | 70 | Atomics.NUMF64INTS = 1; 71 | 72 | // Atomically load fab[fidx] and return it. 73 | 74 | Atomics.float64Load = function (fab, fidx) { 75 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 76 | ; 77 | try { 78 | return fab[fidx]; 79 | } 80 | finally { 81 | Atomics.store(iab, iidx, 0); 82 | } 83 | }; 84 | 85 | // Atomically store v in fab[fidx]. Returns v. 86 | 87 | Atomics.float64Store = function (fab, fidx, v) { 88 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 89 | ; 90 | try { 91 | fab[fidx] = v; 92 | return v; 93 | } 94 | finally { 95 | Atomics.store(iab, iidx, 0); 96 | } 97 | }; 98 | 99 | // Atomically compareExchange fab[fidx]: if its value is expected 100 | // then replace it with updated. Returns the old value in the 101 | // cell. 102 | // 103 | // Equality is representation equality, which is a bit expensive 104 | // to simulate properly: 0 and NaN must be handled specially. 105 | // 106 | // There's an API problem here in that the 'old' value that is 107 | // returned is the only indication of whether an exchange was 108 | // performed, and client code must do the same song and dance for 109 | // equality checking again. In C++11 a flag is additionally 110 | // returned to indicate whether the exchange took place. 111 | 112 | Atomics.float64CompareExchange = function (fab, fidx, expected, update) { 113 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 114 | ; 115 | try { 116 | var v = fab[fidx]; 117 | // Is the "fast path" an optimization or not? The 118 | // expected case is that we do get to replace the value, 119 | // so I think it is, but I have no data. And does it 120 | // matter? 121 | if (v != 0 && v == expected) // +0 != -0 122 | fab[fidx] = update; 123 | else { 124 | _f64tmp[0] = v; 125 | _f64tmp[1] = expected; 126 | if (_i32tmp[0] == _i32tmp[2] && _i32tmp[1] == _i32tmp[3]) 127 | fab[fidx] = update; 128 | } 129 | return v; 130 | } 131 | finally { 132 | Atomics.store(iab, iidx, 0); 133 | } 134 | }; 135 | 136 | // Atomically add v to fab[fidx]. Returns the old value in the cell. 137 | 138 | Atomics.float64Add = function (fab, fidx, v) { 139 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 140 | ; 141 | try { 142 | var w = fab[fidx]; 143 | fab[fidx] += v; 144 | return w; 145 | } 146 | finally { 147 | Atomics.store(iab, iidx, 0); 148 | } 149 | }; 150 | 151 | // Atomically subtract v from fab[fidx]. Returns the old value in 152 | // the cell. 153 | 154 | Atomics.float64Sub = function (fab, fidx, v) { 155 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 156 | ; 157 | try { 158 | var w = fab[fidx]; 159 | fab[fidx] -= v; 160 | return w; 161 | } 162 | finally { 163 | Atomics.store(iab, iidx, 0); 164 | } 165 | }; 166 | })(); 167 | } 168 | 169 | -------------------------------------------------------------------------------- /src/int64atomics.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Atomic operations on int64 arrays. 6 | // 7 | // This polyfill provides these methods: 8 | // 9 | // Atomics.int64Load 10 | // Atomics.int64Store 11 | // Atomics.int64CompareExchange 12 | // Atomics.int64Add 13 | // Atomics.int64Sub 14 | // Atomics.int64And 15 | // Atomics.int64Or 16 | // Atomics.int64Xor 17 | // 18 | // and this getter: 19 | // 20 | // Atomics.H 21 | // 22 | // and this initialization function: 23 | // 24 | // Atomics.int64Init 25 | // 26 | // An int64 value is represented as two int32 values. On input, they 27 | // are passed as two parameters, high and low in that order. On 28 | // output, the atomic ops return the low part of the result and stash 29 | // the high part of the result, which can be retrieved with Atomics.H. 30 | // The intent is that the JIT will be able to optimize the latter 31 | // operation and remove most overhead, and we will not pay for an 32 | // object allocation, and we will be able to use (eventual) native 33 | // operations within asm.js code. 34 | // 35 | // An int64 array is represented as an int32 array of even length. A 36 | // valid index into an int64 array is always even. 37 | // 38 | // In the paragraphs above, "int32" always means "int32 exclusively", 39 | // never "int32 or uint32". 40 | // 41 | // In the polyfill, if an operation throws then the coordination word 42 | // is left in a state where it will not impede progress of other 43 | // accessing threads. Each data word may or may not be updated 44 | // however. (A native implementation will likely do better.) 45 | 46 | // Implementation note: We're assuming little endian for now. 47 | 48 | if (!Atomics.hasOwnProperty("int64Init")) { 49 | (function () { 50 | var iab = null; 51 | var iidx = 0; 52 | var stash = 0; 53 | 54 | // int64Init must be called once with a Int32Array and an 55 | // index within that array that represents the start of a 56 | // range of Atomics.NUMI64INTS integers. The shared memory 57 | // locations denoted by those values should be the same in all 58 | // agents, and they must be initialized to zero before the 59 | // first such call is made. 60 | 61 | Atomics.int64Init = function (iab_, iidx_) { 62 | iab = iab_; 63 | iidx = iidx_; 64 | }; 65 | 66 | Atomics.NUMI64INTS = 1; 67 | 68 | // Atomically load (hi,lo) from lab[lidx]. Stash hi and return lo. 69 | 70 | Atomics.int64Load = function (lab, lidx) { 71 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 72 | ; 73 | try { 74 | var lo = lab[lidx]; 75 | var hi = lab[lidx+1]; 76 | stash = hi; 77 | return lo; 78 | } 79 | finally { 80 | Atomics.store(iab, iidx, 0); 81 | } 82 | }; 83 | 84 | // Atomics.H: Return the most recent stashed value. 85 | 86 | Object.defineProperties(Atomics, {"H": {get: function() { return stash; }}}); 87 | 88 | // Atomically store (hi,lo) at lab[lidx]. Returns nothing. 89 | 90 | Atomics.int64Store = function (lab, lidx, value_hi, value_lo) { 91 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 92 | ; 93 | try { 94 | lab[lidx] = value_lo; 95 | lab[lidx+1] = value_hi; 96 | } 97 | finally { 98 | Atomics.store(iab, iidx, 0); 99 | } 100 | }; 101 | 102 | // Atomically load (hi,lo) from lab[lidx] and compare to (expected_hi,expected_lo); 103 | // if equal, update lab[idx] with (update_hi,update_lo). Stash hi and return lo. 104 | 105 | Atomics.int64CompareExchange = function (lab, lidx, expected_hi, expected_lo, update_hi, update_lo) { 106 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 107 | ; 108 | try { 109 | var vlo = lab[lidx]; 110 | var vhi = lab[lidx+1]; 111 | if (vlo == expected_lo && vhi == expected_hi) { 112 | lab[lidx] = update_lo; 113 | lab[lidx+1] = update_hi; 114 | } 115 | stash = vhi; 116 | return vlo; 117 | } 118 | finally { 119 | Atomics.store(iab, iidx, 0); 120 | } 121 | }; 122 | 123 | // Atomically load (hi,lo) from lab[lidx]; let (r,s) = (hi,lo)+(value_hi,value_lo); 124 | // store (r,s) at lab[idx]. Stash hi and return lo. 125 | 126 | Atomics.int64Add = function (lab, lidx, value_hi, value_lo) { 127 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 128 | ; 129 | try { 130 | var lo = lab[lidx]; 131 | var hi = lab[lidx+1]; 132 | var rlo = lo + value_lo; 133 | lab[lidx] = rlo|0; 134 | var carry = (lo ^ value_lo) < 0 || (rlo ^ lo) >= 0 ? 1 : 0; 135 | var rhi = (hi + value_hi + carry) 136 | lab[lidx+1] = rhi|0; 137 | stash = hi; 138 | return lo; 139 | } 140 | finally { 141 | Atomics.store(iab, iidx, 0); 142 | } 143 | }; 144 | 145 | // Atomically load (hi,lo) from lab[lidx]; let (r,s) = (hi,lo)-(value_hi,value_lo); 146 | // store (r,s) at lab[idx]. Stash hi and return lo. 147 | 148 | Atomics.int64Sub = function (lab, lidx, value_hi, value_lo) { 149 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 150 | ; 151 | try { 152 | var lo = lab[lidx]; 153 | var hi = lab[lidx+1]; 154 | var rlo = lo - value_lo; 155 | lab[lidx] = rlo|0; 156 | var borrow = (lo ^ value_lo) >= 0 || (rlo ^ lo) >= 0 ? 1 : 0; 157 | var rhi = hi - value_hi - borrow; 158 | lab[lidx+1] = rhi|0; 159 | stash = hi; 160 | return lo; 161 | } 162 | finally { 163 | Atomics.store(iab, iidx, 0); 164 | } 165 | }; 166 | 167 | // Atomically load (hi,lo) from lab[lidx]; let (r,s) = (hi,lo) & (value_hi,value_lo); 168 | // store (r,s) at lab[idx]. Stash hi and return lo. 169 | 170 | Atomics.int64And = function (lab, lidx, value_hi, value_lo) { 171 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 172 | ; 173 | try { 174 | var lo = lab[lidx]; 175 | var hi = lab[lidx+1]; 176 | lab[lidx] = lo & value_lo; 177 | lab[lidx+1] = hi & value_hi; 178 | stash = hi; 179 | return lo; 180 | } 181 | finally { 182 | Atomics.store(iab, iidx, 0); 183 | } 184 | }; 185 | 186 | // Atomically load (hi,lo) from lab[lidx]; let (r,s) = (hi,lo) | (value_hi,value_lo); 187 | // store (r,s) at lab[idx]. Stash hi and return lo. 188 | 189 | Atomics.int64Or = function (lab, lidx, value_hi, value_lo) { 190 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 191 | ; 192 | try { 193 | var lo = lab[lidx]; 194 | var hi = lab[lidx+1]; 195 | lab[lidx] = lo | value_lo; 196 | lab[lidx+1] = hi | value_hi; 197 | stash = hi; 198 | return lo; 199 | } 200 | finally { 201 | Atomics.store(iab, iidx, 0); 202 | } 203 | }; 204 | 205 | // Atomically load (hi,lo) from lab[lidx]; let (r,s) = (hi,lo) ^ (value_hi,value_lo); 206 | // store (r,s) at lab[idx]. Stash hi and return lo. 207 | 208 | Atomics.int64Xor = function (lab, lidx, value_hi, value_lo) { 209 | while (Atomics.compareExchange(iab, iidx, 0, -1) != 0) 210 | ; 211 | try { 212 | var lo = lab[lidx]; 213 | var hi = lab[lidx+1]; 214 | lab[lidx] = lo ^ value_lo; 215 | lab[lidx+1] = hi ^ value_hi; 216 | stash = hi; 217 | return lo; 218 | } 219 | finally { 220 | Atomics.store(iab, iidx, 0); 221 | } 222 | }; 223 | })(); 224 | } 225 | 226 | -------------------------------------------------------------------------------- /src/intqueue.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* 6 | * Simple multi-producer and multi-consumer shared-memory queue for 7 | * transmitting arrays of Int32 values - a useful building block for 8 | * other mechanisms. 9 | * 10 | * TODO: perhaps generalize this to bundles of same-typed elements. 11 | */ 12 | 13 | // REQUIRE 14 | // synchronic.js 15 | // arena.js 16 | 17 | // Internal constants. 18 | const _IQ_USED = 0; 19 | const _IQ_HEAD = 1; 20 | const _IQ_TAIL = 2; 21 | 22 | /* 23 | * Construct an IntQueue object in any agent. 24 | * 25 | * sab must be a SharedArrayBuffer. 26 | * offset must be a valid offset within that array. 27 | * length must be the length of a segment within that array. 28 | * length-offset must have space for metadata and queue data. 29 | * An upper bound on metadata is given by IntQueue.NUMBYTES. 30 | * 31 | * Constructors may be called concurrently in all agents provided the 32 | * memory that will be used has been zero-filled and the zeroes are 33 | * visible in the calling agent when the constructor is called. 34 | */ 35 | function IntQueue(sab, offset, length) { 36 | var intSize = 4; 37 | var synSize = SynchronicInt32.BYTES_PER_ELEMENT; 38 | var synAlign = SynchronicInt32.BYTE_ALIGNMENT; 39 | var a = new ArrayBufferArena(sab, offset, length); 40 | 41 | this._spaceAvailable = new SynchronicInt32(sab, a.alloc(synSize, synAlign)); 42 | this._dataAvailable = new SynchronicInt32(sab, a.alloc(synSize, synAlign)); 43 | this._lock = new SynchronicInt32(sab, a.alloc(synSize, synAlign)); 44 | 45 | this._meta = new Int32Array(sab, a.alloc(intSize*3, intSize), 3); 46 | var qlen = Math.floor(a.available(intSize) / intSize); 47 | this._queue = new Int32Array(sab, a.alloc(intSize*qlen, intSize), qlen); 48 | } 49 | 50 | /* 51 | * The number of bytes needed for metadata (upper bound, allowing for 52 | * bad alignment etc). 53 | */ 54 | IntQueue.NUMBYTES = 64; 55 | 56 | /* 57 | * Enters an element into the queue, waits until space is available or 58 | * until t milliseconds (undefined == indefinite wait) have passed. 59 | * 60 | * ints is a dense Array of Int32 values. 61 | * Returns true if it succeeded, false if it timed out. 62 | */ 63 | IntQueue.prototype.enqueue = function(ints, t) { 64 | var required = ints.length + 1; 65 | 66 | if (!this._acquireWithSpaceAvailable(required, t)) 67 | return false; 68 | 69 | var q = this._queue; 70 | var qlen = q.length; 71 | var tail = this._meta[_IQ_TAIL]; 72 | q[tail] = ints.length; 73 | tail = (tail + 1) % qlen; 74 | for ( var i=0 ; i < ints.length ; i++ ) { 75 | q[tail] = ints[i]; 76 | tail = (tail + 1) % qlen; 77 | } 78 | this._meta[_IQ_TAIL] = tail; 79 | this._meta[_IQ_USED] += required; 80 | 81 | this._releaseWithDataAvailable(); 82 | return true; 83 | } 84 | 85 | /* 86 | * Returns an element from the queue if there's one, or waits up to t 87 | * milliseconds (undefined == indefinite wait) for one to appear, 88 | * returning null if none appears in that time. 89 | * 90 | * The element is returned as a dense Array of Int32 values. 91 | */ 92 | IntQueue.prototype.dequeue = function (t) { 93 | if (!this._acquireWithDataAvailable(t)) 94 | return null; 95 | 96 | var A = []; 97 | var q = this._queue; 98 | var qlen = q.length; 99 | var head = this._meta[_IQ_HEAD]; 100 | var count = q[head]; 101 | head = (head + 1) % qlen; 102 | while (count-- > 0) { 103 | A.push(q[head]); 104 | head = (head + 1) % qlen; 105 | } 106 | this._meta[_IQ_HEAD] = head; 107 | this._meta[_IQ_USED] -= A.length + 1; 108 | 109 | this._releaseWithSpaceAvailable(); 110 | return A; 111 | } 112 | 113 | // Internal code below this point 114 | 115 | IntQueue.prototype._acquireWithSpaceAvailable = function (required, t) { 116 | var limit = typeof t != "undefined" ? Date.now() + t : Number.POSITIVE_INFINITY; 117 | for (;;) { 118 | this._acquire(); 119 | var length = this._queue.length; 120 | if (length - this._meta[_IQ_USED] >= required) 121 | return true; 122 | var probe = this._spaceAvailable.load(); 123 | this._release(); 124 | if (required > length) 125 | throw new Error("Queue will never accept " + required + " words"); 126 | var remaining = limit - Date.now(); 127 | if (remaining <= 0) 128 | return false; 129 | this._spaceAvailable.expectUpdate(probe, remaining); 130 | } 131 | } 132 | 133 | IntQueue.prototype._acquireWithDataAvailable = function (t) { 134 | var limit = typeof t != "undefined" ? Date.now() + t : Number.POSITIVE_INFINITY; 135 | for (;;) { 136 | this._acquire(); 137 | if (this._meta[_IQ_USED] > 0) 138 | return true; 139 | var probe = this._dataAvailable.load(); 140 | this._release(); 141 | var remaining = limit - Date.now(); 142 | if (remaining <= 0) 143 | return false; 144 | this._dataAvailable.expectUpdate(probe, remaining); 145 | } 146 | } 147 | 148 | IntQueue.prototype._releaseWithSpaceAvailable = function() { 149 | this._spaceAvailable.add(1); 150 | this._release(); 151 | } 152 | 153 | IntQueue.prototype._releaseWithDataAvailable = function() { 154 | this._dataAvailable.add(1); 155 | this._release(); 156 | } 157 | 158 | // The locking protocol must not access the _meta data. 159 | 160 | IntQueue.prototype._acquire = function () { 161 | while (this._lock.compareExchange(0, 1) != 0) 162 | this._lock.expectUpdate(1, Number.POSITIVE_INFINITY); 163 | } 164 | 165 | IntQueue.prototype._release = function () { 166 | this._lock.store(0); 167 | } 168 | -------------------------------------------------------------------------------- /src/lock.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Simple, standalone lock and condition variable abstractions. 6 | // 2015-01-13 / lhansen@mozilla.com 7 | 8 | ////////////////////////////////////////////////////////////////////// 9 | // 10 | // Locks. 11 | // 12 | // Locks are JS objects that use some shared memory for private data. 13 | // The number of shared int32 locations needed is given by 14 | // Lock.NUMINTS. The shared memory for a lock should be initialized 15 | // once by calling Lock.initialize() on the memory, before 16 | // constructing the first Lock object in any agent. 17 | // 18 | // 19 | // Implementation note: 20 | // Lock code taken from http://www.akkadia.org/drepper/futex.pdf. 21 | // Lock states: 22 | // 0: unlocked 23 | // 1: locked with no waiters 24 | // 2: locked with possible waiters 25 | // 26 | // This could be built on Synchronic instead, but this code predates 27 | // the Synchronic facility. 28 | 29 | "use strict"; 30 | 31 | // Create a lock object. 32 | // 33 | // 'iab' must be a Int32Array mapping shared memory. 34 | // 'ibase' must be a valid index in iab, the first of Lock.NUMINTS reserved for the lock. 35 | // 36 | // iab and ibase will be exposed on Lock. 37 | function Lock(iab, ibase) { 38 | if (!(iab instanceof Int32Array && ibase|0 == ibase && ibase >= 0 && ibase+Lock.NUMINTS <= iab.length)) 39 | throw new Error("Bad arguments to Lock constructor: " + iab + " " + ibase); 40 | this.iab = iab; 41 | this.ibase = ibase; 42 | } 43 | 44 | // Number of shared Int32 locations needed by the lock. 45 | Lock.NUMINTS = 1; 46 | 47 | // Initialize shared memory for a lock, before constructing the 48 | // worker-local Lock objects on that memory. 49 | // 50 | // 'iab' must be an Int32Array mapping shared memory. 51 | // 'ibase' must be a valid index in iab, the first of Lock.NUMINTS reserved 52 | // for the lock. 53 | // 54 | // Returns 'ibase'. 55 | Lock.initialize = 56 | function (iab, ibase) { 57 | if (!(iab instanceof Int32Array && ibase|0 == ibase && ibase >= 0 && ibase+Lock.NUMINTS <= iab.length)) 58 | throw new Error("Bad arguments to Lock initializer: " + iab + " " + ibase); 59 | Atomics.store(iab, ibase, 0); 60 | return ibase; 61 | }; 62 | 63 | // Acquire the lock, or block until we can. Locking is not recursive: 64 | // you must not hold the lock when calling this. 65 | Lock.prototype.lock = 66 | function () { 67 | const iab = this.iab; 68 | const stateIdx = this.ibase; 69 | var c; 70 | if ((c = Atomics.compareExchange(iab, stateIdx, 0, 1)) != 0) { 71 | do { 72 | if (c == 2 || Atomics.compareExchange(iab, stateIdx, 1, 2) != 0) 73 | Atomics.wait(iab, stateIdx, 2, Number.POSITIVE_INFINITY); 74 | } while ((c = Atomics.compareExchange(iab, stateIdx, 0, 2)) != 0); 75 | } 76 | }; 77 | 78 | // Attempt to acquire the lock, return true if it was acquired, false 79 | // if not. Locking is not recursive: you must not hold the lock when 80 | // calling this. 81 | Lock.prototype.tryLock = 82 | function () { 83 | const iab = this.iab; 84 | const stateIdx = this.ibase; 85 | return Atomics.compareExchange(iab, stateIdx, 0, 1) == 0; 86 | }; 87 | 88 | // Unlock a lock that is held. Anyone can unlock a lock that is held; 89 | // nobody can unlock a lock that is not held. 90 | Lock.prototype.unlock = 91 | function () { 92 | const iab = this.iab; 93 | const stateIdx = this.ibase; 94 | var v0 = Atomics.sub(iab, stateIdx, 1); 95 | // Wake up a waiter if there are any 96 | if (v0 != 1) { 97 | Atomics.store(iab, stateIdx, 0); 98 | Atomics.wake(iab, stateIdx, 1); 99 | } 100 | }; 101 | 102 | Lock.prototype.toString = 103 | function () { 104 | return "Lock:{ibase:" + this.ibase +"}"; 105 | }; 106 | 107 | ////////////////////////////////////////////////////////////////////// 108 | // 109 | // Condition variables. 110 | // 111 | // Condition variables are JS objects that use some shared memory for 112 | // private data. The number of shared int32 locations needed is given 113 | // by Cond.NUMINTS. The shared memory for a condition variable should 114 | // be initialized once by calling Cond.initialize() on the memory, 115 | // before constructing the first Cond object in any agent. 116 | // 117 | // 118 | // Implementation note: 119 | // The condvar code is based on http://locklessinc.com/articles/mutex_cv_futex, 120 | // though modified because some optimizations in that code don't quite apply. 121 | // 122 | // Again, using Synchronic might be easier. 123 | 124 | // Create a condition variable that can wait on a lock. 125 | // 126 | // 'lock' is an instance of Lock. 127 | // 'ibase' must be a valid index in lock.iab, the first of Cond.NUMINTS reserved 128 | // for the condition. 129 | // 130 | // lock.iab and ibase will be exposed on Cond. 131 | function Cond(lock, ibase) { 132 | if (!(lock instanceof Lock && ibase|0 == ibase && ibase >= 0 && ibase+Cond.NUMINTS <= lock.iab.length)) 133 | throw new Error("Bad arguments to Cond constructor: " + lock + " " + ibase); 134 | this.iab = lock.iab; 135 | this.ibase = ibase; 136 | this.lock = lock; 137 | } 138 | 139 | // Number of shared Int32 locations needed by the condition variable. 140 | Cond.NUMINTS = 1; 141 | 142 | // Initialize shared memory for a condition variable, before 143 | // constructing the worker-local Cond objects on that memory. 144 | // 145 | // Returns 'ibase'. 146 | Cond.initialize = 147 | function (iab, ibase) { 148 | if (!(iab instanceof Int32Array && ibase|0 == ibase && ibase >= 0 && ibase+Cond.NUMINTS <= iab.length)) 149 | throw new Error("Bad arguments to Cond initializer: " + iab + " " + ibase); 150 | Atomics.store(iab, ibase, 0); 151 | return ibase; 152 | }; 153 | 154 | // Atomically unlocks the cond's lock and wait for a wakeup on the 155 | // cond. If there were waiters on lock then they are woken as the 156 | // lock is unlocked. 157 | // 158 | // The caller must hold the lock when calling wait(). When wait() 159 | // returns the lock will once again be held. 160 | Cond.prototype.wait = 161 | function () { 162 | const iab = this.iab; 163 | const seqIndex = this.ibase; 164 | const seq = Atomics.load(iab, seqIndex); 165 | const lock = this.lock; 166 | lock.unlock(); 167 | Atomics.wait(iab, seqIndex, seq, Number.POSITIVE_INFINITY); 168 | lock.lock(); 169 | }; 170 | 171 | // Wakes one waiter on cond. The cond's lock must be held by the 172 | // caller of wake(). 173 | Cond.prototype.wake = 174 | function () { 175 | const iab = this.iab; 176 | const seqIndex = this.ibase; 177 | Atomics.add(iab, seqIndex, 1); 178 | Atomics.wake(iab, seqIndex, 1); 179 | }; 180 | 181 | // Wakes all waiters on cond. The cond's lock must be held by the 182 | // caller of wakeAll(). 183 | Cond.prototype.wakeAll = 184 | function () { 185 | const iab = this.iab; 186 | const seqIndex = this.ibase; 187 | Atomics.add(iab, seqIndex, 1); 188 | // Optimization opportunity: only wake one, and requeue the others 189 | // (in such a way as to obey the locking protocol properly). 190 | Atomics.wake(iab, seqIndex, 65535); 191 | }; 192 | 193 | Cond.prototype.toString = 194 | function () { 195 | return "Cond:{ibase:" + this.ibase +"}"; 196 | }; 197 | -------------------------------------------------------------------------------- /src/message.js: -------------------------------------------------------------------------------- 1 | // If the message is an array whose first element is a string that 2 | // matches "tag" then consume the message and invoke fn on its data. 3 | 4 | function dispatchMessage(target, tag, fn) { 5 | if (typeof tag != "string") 6 | throw new Error("Tag to dispatchMessage must be string: " + tag); 7 | 8 | target.addEventListener("message", function (ev) { 9 | if (Array.isArray(ev.data) && ev.data.length >= 1 && ev.data[0] === tag) { 10 | ev.stopImmediatePropagation(); 11 | fn(ev.data); 12 | } 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/bump-alloc-shelltest.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Sanity tests for the bump allocator in the JS shell. This requires 6 | // a JS shell built with the shared memory types. 7 | 8 | load("../src/bump-alloc.js"); 9 | load("../src/barrier.js"); 10 | 11 | var nbytes = 1024; 12 | var n = nbytes + BumpAlloc.NUMBYTES; 13 | var padding = 32; 14 | var sab = new SharedArrayBuffer(n + padding*2); 15 | var base = padding; 16 | 17 | // 32 bytes on each side is padding, we'll check at the end that they're untouched 18 | var tmp = new Uint8Array(sab); 19 | for ( var i=0 ; i < padding ; i++ ) { 20 | tmp[i] = 0xDE; 21 | tmp[tmp.length-1-i] = 0xBE; 22 | } 23 | 24 | BumpAlloc.initialize(sab, base, n); 25 | 26 | var ba = new BumpAlloc(sab, base); 27 | var ba2 = new BumpAlloc(sab, base); 28 | 29 | // Sanity 30 | assertEq(ba.Int8Array.length >= 1024, true); 31 | assertEq(ba.Float64Array.length >= 128, true); 32 | 33 | var bottom = ba.mark(); 34 | 35 | ////////////////////////////////////////////////////////////////////// 36 | // White-box tests. 37 | 38 | // The heap limit is where we set it, plus page zero 39 | assertEq(ba._limit, _BA_PAGEZEROSZ+nbytes); 40 | 41 | // The first object is at the heap base. 42 | var v = ba.allocInt32(1); 43 | assertEq(v > 0, true); 44 | assertEq(v, _BA_PAGEZEROSZ >>> 2); 45 | 46 | // End white-box 47 | ////////////////////////////////////////////////////////////////////// 48 | 49 | // Arrays alias, even across allocators 50 | assertEq(ba.Int8Array.buffer, sab); 51 | assertEq(ba.Int8Array.buffer, ba.Int32Array.buffer); 52 | assertEq(ba.Int8Array.byteOffset, ba.Int32Array.byteOffset); 53 | assertEq(ba2.Int8Array.byteOffset, ba.Int8Array.byteOffset); 54 | 55 | // No padding 56 | var first = ba.mark(); 57 | ba.allocInt32(10); 58 | var next = ba.mark(); 59 | assertEq(first + 40, next); 60 | 61 | // Mark/Release works as expected 62 | ba.release(first); 63 | assertEq(first, ba.mark()); 64 | 65 | // Allocating arrays works too 66 | var a = ba.allocInt32Array(10); 67 | assertEq(a.length, 10); 68 | 69 | // No padding, and not overlapping 70 | var b = ba.allocInt32Array(10); 71 | assertEq(a.byteOffset + 40, b.byteOffset); 72 | 73 | // Precise allocation semantics 74 | ba.release(bottom); 75 | for ( var i=0 ; i < nbytes/8 ; i++ ) 76 | assertEq(ba.allocInt8(1) != 0, true); 77 | assertEq(ba.allocInt8(1), 0); 78 | 79 | ba.release(bottom); 80 | for ( var i=0 ; i < nbytes/8 ; i++ ) 81 | assertEq(ba.allocInt16(1) != 0, true); 82 | assertEq(ba.allocInt16(1), 0); 83 | 84 | ba.release(bottom); 85 | for ( var i=0 ; i < nbytes/8 ; i++ ) 86 | assertEq(ba.allocInt32(1) != 0, true); 87 | assertEq(ba.allocInt32(1), 0); 88 | 89 | ba.release(bottom); 90 | for ( var i=0 ; i < nbytes/8 ; i++ ) 91 | assertEq(ba.allocFloat32(1) != 0, true); 92 | assertEq(ba.allocFloat32(1), 0); 93 | 94 | ba.release(bottom); 95 | for ( var i=0 ; i < nbytes/8 ; i++ ) 96 | assertEq(ba.allocFloat64(1) != 0, true); 97 | assertEq(ba.allocFloat64(1), 0); 98 | 99 | // Scribble scribble 100 | ba.release(bottom); 101 | for ( var i=0 ; i < nbytes ; i++ ) 102 | ba.Int32Array[i] = 0xCC; 103 | 104 | // Make sure this is not allowed 105 | ba.release(bottom); 106 | ba.allocFloat64(1); 107 | var exn = false; 108 | try { 109 | ba.release(ba.mark() + 8); 110 | } 111 | catch (e) { 112 | exn = true; 113 | } 114 | assertEq(exn, true); 115 | 116 | // Check that padding is untouched 117 | for ( var i=0 ; i < padding ; i++ ) { 118 | assertEq(tmp[i], 0xDE); 119 | assertEq(tmp[tmp.length-1-i], 0xBE); 120 | } 121 | 122 | // Test contention / mutual exclusion. 123 | 124 | var numWorkers = 2; 125 | 126 | var size2 = 32768*(numWorkers+2); // For main + overhead 127 | var sab2 = new SharedArrayBuffer(size2); 128 | var base2 = 0; 129 | BumpAlloc.initialize(sab2, base2, size2); 130 | var ba2 = new BumpAlloc(sab2, base2); 131 | 132 | var numIter = 10; // Number of times to run the test 133 | var numObjs = 1000; // Number of objects to allocate every iteration 134 | var objSize = 4; // Words per object 135 | var pattern = 0xA5A5A5A5|0; // Poison pattern in main thread 136 | 137 | var barrierIdx = ba2.allocInt32(Barrier.NUMINTS); 138 | Barrier.initialize(ba2.Int32Array, barrierIdx, numWorkers+1); 139 | var barrier = new Barrier(ba2.Int32Array, barrierIdx); 140 | 141 | var mark = ba2.mark(); 142 | 143 | assertEq(mark % 8, 0); 144 | 145 | function allocLoop(isMaster) { 146 | for ( var iter=0 ; iter < numIter ; iter++ ) { 147 | var as = []; 148 | for ( var i=0 ; i < numObjs ; i++ ) 149 | as.push(ba2.allocInt32(objSize)); 150 | var ia = ba2.Int32Array; 151 | for ( var x of as ) 152 | for ( var i=0 ; i < objSize ; i++ ) { 153 | if (ia[x+i] != 0) 154 | throw new Error("Wrong: on init"); 155 | ia[x+i] = pattern; 156 | } 157 | barrier.enter(); 158 | for ( var x of as ) 159 | for ( var i=0 ; i < objSize ; i++ ) { 160 | if (ia[x+i] != pattern) 161 | throw new Error("Wrong: on check"); 162 | ia[x+i] = 0; 163 | } 164 | barrier.enter(); 165 | if (isMaster) 166 | ba2.release(mark); 167 | barrier.enter(); 168 | } 169 | } 170 | 171 | setSharedArrayBuffer(sab2); 172 | 173 | var prog = ` 174 | load("../src/bump-alloc.js"); 175 | load("../src/barrier.js"); 176 | var sab2 = getSharedArrayBuffer(); 177 | var base2 = ${base2}; 178 | var ba2 = new BumpAlloc(sab2, base2); 179 | var barrierIdx = ${barrierIdx}; 180 | var barrier = new Barrier(ba2.Int32Array, barrierIdx); 181 | var numIter = ${numIter}; 182 | var numObjs = ${numObjs}; 183 | var objSize = ${objSize}; 184 | var pattern = 0xC3C3C3C3|0; 185 | ${allocLoop.toSource()} 186 | allocLoop(false); 187 | ` 188 | 189 | for ( var i=0 ; i < numWorkers ; i++ ) 190 | evalInWorker(prog); 191 | 192 | allocLoop(true); 193 | 194 | print("Done"); 195 | -------------------------------------------------------------------------------- /test/channel-shelltest.js: -------------------------------------------------------------------------------- 1 | // Test the message channel in ../src/channel.js. 2 | 3 | load("../src/arena.js"); 4 | load("../src/synchronic.js"); 5 | load("../src/intqueue.js"); 6 | load("../src/marshaler.js"); 7 | load("../src/channel.js"); 8 | 9 | // Basic test 10 | 11 | var sab = new SharedArrayBuffer(4096); // Keep it short for better testing 12 | 13 | var q = new ChannelSender(sab, 0, sab.byteLength); 14 | 15 | setSharedArrayBuffer(sab); 16 | 17 | var iterations = 10000; 18 | 19 | evalInWorker( 20 | ` 21 | load("../src/arena.js"); 22 | load("../src/synchronic.js"); 23 | load("../src/intqueue.js"); 24 | load("../src/marshaler.js"); 25 | load("../src/channel.js"); 26 | 27 | var iterations=${iterations}; 28 | var sab = getSharedArrayBuffer(); 29 | var q = new ChannelReceiver(sab, 0, sab.byteLength); 30 | 31 | for ( var i=0 ; i < iterations ; i++ ) { 32 | var v = q.receive(); 33 | assertEq(v.ho, i); 34 | var w = v.hi; 35 | for ( var j=0 ; j < w.length ; j++ ) 36 | assertEq(w[j], i+j); 37 | assertEq(typeof v.abner, "string"); 38 | assertEq(v.abner, ""+i); 39 | } 40 | `); 41 | 42 | for ( var i=0 ; i < iterations ; i++ ) { 43 | q.send({hi:[i,i+1,i+2,i+3,i+4], ho:i, abner:i+""}); 44 | } 45 | -------------------------------------------------------------------------------- /test/float64atomics-shelltest.js: -------------------------------------------------------------------------------- 1 | load("../src/float64atomics.js"); 2 | 3 | var xx = new Int32Array(new SharedArrayBuffer(Atomics.NUMF64INTS*Int32Array.BYTES_PER_ELEMENT)); 4 | var fa = new Float64Array(new SharedArrayBuffer(10*Float64Array.BYTES_PER_ELEMENT)); 5 | 6 | Atomics.float64Init(xx, 0); 7 | 8 | fa[4] = -2; 9 | assertEq(Atomics.float64Load(fa, 4), -2); 10 | 11 | assertEq(Atomics.float64CompareExchange(fa, 4, -2, 7), -2); 12 | assertEq(fa[4], 7); 13 | 14 | assertEq(Atomics.float64Store(fa, 4, -10), -10); 15 | 16 | assertEq(fa[4], -10); 17 | 18 | assertEq(Atomics.float64Add(fa, 4, 20), -10); 19 | 20 | assertEq(fa[4], 10); 21 | 22 | assertEq(Atomics.float64Sub(fa, 4, 15), 10); 23 | 24 | assertEq(fa[4], -5); 25 | 26 | fa[4] = -0; 27 | var v = Atomics.float64CompareExchange(fa, 4, 0, 7); 28 | assertEq(fa[4], -0); 29 | 30 | print("Done"); 31 | -------------------------------------------------------------------------------- /test/int64atomics-shelltest.js: -------------------------------------------------------------------------------- 1 | load("../src/int64atomics.js"); 2 | 3 | // Little endian, so lsw first 4 | function int64Set(ia, x, hi, lo) { 5 | ia[4] = lo; 6 | ia[5] = hi; 7 | } 8 | 9 | function int64RefLo(ia, x) { 10 | return ia[x]; 11 | } 12 | 13 | function int64RefHi(ia, x) { 14 | return ia[x+1]; 15 | } 16 | 17 | function int64Ref(ia, x) { 18 | return {hi: int64RefHi(ia, x), lo: int64RefLo(ia, x) }; 19 | } 20 | 21 | var xx = new Int32Array(new SharedArrayBuffer(Atomics.NUMI64INTS*Int32Array.BYTES_PER_ELEMENT)); 22 | var ia = new Int32Array(new SharedArrayBuffer(10*Int32Array.BYTES_PER_ELEMENT)); 23 | 24 | Atomics.int64Init(xx, 0); 25 | 26 | int64Set(ia, 4, -1, -2); 27 | 28 | assertEq(Atomics.int64Load(ia, 4), -2); 29 | assertEq(Atomics.H, -1); 30 | 31 | assertEq(Atomics.int64CompareExchange(ia, 4, -1, -2, 0, 7), -2); 32 | assertEq(Atomics.H, -1); 33 | 34 | assertEq(int64RefHi(ia, 4), 0); 35 | assertEq(int64RefLo(ia, 4), 7); 36 | 37 | Atomics.int64Store(ia, 4, -1, -10); 38 | 39 | assertEq(int64RefHi(ia, 4), -1); 40 | assertEq(int64RefLo(ia, 4), -10); 41 | 42 | Atomics.int64Add(ia, 4, 0, 20); 43 | 44 | assertEq(int64RefHi(ia, 4), 0); 45 | assertEq(int64RefLo(ia, 4), 10); 46 | 47 | Atomics.int64Sub(ia, 4, 0, 15); 48 | 49 | assertEq(int64RefHi(ia, 4), -1); 50 | assertEq(int64RefLo(ia, 4), -5); 51 | 52 | Atomics.int64Store(ia, 4, 0x55555555, 0x55555555); 53 | Atomics.int64Or(ia, 4, 0xAAAAAAAA|0, 0xAAAAAAAA|0); 54 | 55 | assertEq(int64RefHi(ia, 4), -1); 56 | assertEq(int64RefLo(ia, 4), -1); 57 | 58 | Atomics.int64Store(ia, 4, 0x55555555, 0x55555555); 59 | Atomics.int64And(ia, 4, 0x33333333, 0xDDDDDDDD|0); 60 | 61 | assertEq(int64RefHi(ia, 4), 0x11111111); 62 | assertEq(int64RefLo(ia, 4), 0x55555555); 63 | 64 | Atomics.int64Xor(ia, 4, 0x33333333, 0x77777777); 65 | 66 | assertEq(int64RefHi(ia, 4), 0x22222222); 67 | assertEq(int64RefLo(ia, 4), 0x22222222); 68 | 69 | print("Done"); 70 | -------------------------------------------------------------------------------- /test/intqueue-shelltest.js: -------------------------------------------------------------------------------- 1 | load("../src/arena.js"); 2 | load("../src/synchronic.js"); 3 | load("../src/intqueue.js"); 4 | 5 | // Basic test 6 | 7 | var sab = new SharedArrayBuffer(4096); // Keep it short for better testing 8 | 9 | var q = new IntQueue(sab, 0, sab.byteLength); 10 | 11 | setSharedArrayBuffer(sab); 12 | 13 | var iterations = 10000; 14 | 15 | evalInWorker( 16 | ` 17 | load("../src/arena.js"); 18 | load("../src/synchronic.js"); 19 | load("../src/intqueue.js"); 20 | 21 | var iterations=${iterations}; 22 | var sab = getSharedArrayBuffer(); 23 | var q = new IntQueue(sab, 0, sab.byteLength); 24 | 25 | var xs = []; 26 | for ( var i=0 ; i < iterations ; i++ ) { 27 | var v = q.dequeue(); 28 | // if (!(i % 999)) 29 | // print(v.toSource()); 30 | for ( var j=0 ; j < v.length ; j++ ) 31 | assertEq(v[j], i+j); 32 | } 33 | `); 34 | 35 | for ( var i=0 ; i < iterations ; i++ ) { 36 | q.enqueue([i,i+1,i+2,i+3,i+4]); 37 | } 38 | -------------------------------------------------------------------------------- /test/marshaler-shelltest.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Sanity tests for the marshaling code to be run in the shell. 6 | 7 | load("../src/marshaler.js"); 8 | 9 | var m1 = new Marshaler(); 10 | var m2 = new Marshaler(); 11 | var global = this; 12 | 13 | // Illustrate the extensible marshaling by passing a function (as source code). 14 | 15 | var FuncID = m1.generateID("function", null); 16 | 17 | m2.registerID("function", 18 | FuncID, 19 | function (m, vs) { 20 | // Could eval as function definition, but function expression is 21 | // cleaner for the purposes of testing. 22 | return global.eval("(" + m.unmarshal(vs, 1, vs.length-1)[0] + ")"); 23 | }); 24 | 25 | Function.prototype.toMarshaled = 26 | function (m) { 27 | var { values, newSAB } = m.marshal([this.toSource()]); 28 | values.unshift(FuncID); 29 | return values; 30 | }; 31 | 32 | var s1 = new SharedArrayBuffer(100); 33 | var s2 = new SharedArrayBuffer(200); 34 | var i1 = new Int32Array(s1, 20, 5); 35 | var f1 = new Float64Array(s2, 8, 20); 36 | var ab = new ArrayBuffer(20); 37 | var tmp = new Uint8Array(ab); 38 | for ( var i=0 ; i < tmp.length ; i++ ) 39 | tmp[i] = i; 40 | var i32a = new Int32Array(ab, 4, 3); // bytes 3..15 41 | var u8a = new Uint8Array(ab, 10, 10); // bytes 10..19 42 | var a0 = ["hi", 37, ["ho", "hum"],,14]; 43 | var o0 = { foo: "1", bar: a0 }; 44 | var t1 = [undefined, null, true, false, 45 | 37, Math.PI, "foobar", "basic", 46 | s1, s1, s2, i1, 47 | f1, ab, i32a, u8a, 48 | a0, o0, fib ]; 49 | 50 | function fib(n) { 51 | if (n < 2) 52 | return n; 53 | return fib(n-1) + fib(n-2); 54 | } 55 | 56 | var v = m1.marshal(t1); 57 | 58 | assertEq(v.newSAB.length, 2); 59 | assertEq(v.newSAB[0].sab === s1 || v.newSAB[1].sab === s1, true); 60 | assertEq(v.newSAB[0].sab === v.newSAB[1].sab, false); 61 | assertEq(v.newSAB[0].id === v.newSAB[1].id, false); 62 | 63 | assertEq(m1.getSAB(v.newSAB[0].id), v.newSAB[0].sab); 64 | assertEq(m1.getSAB(v.newSAB[1].id), v.newSAB[1].sab); 65 | 66 | assertEq(m1.getSAB(2), null); // Whitebox knowledge: the ID range is dense 67 | 68 | assertEq(m2.registerSAB(v.newSAB[0].sab, v.newSAB[0].id), v.newSAB[0].id); 69 | assertEq(m2.registerSAB(v.newSAB[1].sab, v.newSAB[1].id), v.newSAB[1].id); 70 | 71 | var t2 = m2.unmarshal(v.values, 0, v.values.length); 72 | 73 | assertEq(t1.length, t2.length); 74 | for ( var i=0 ; i < 8 ; i++ ) 75 | assertEq(t1[i], t2[i]); 76 | 77 | assertEq(t2[8] instanceof SharedArrayBuffer, true); 78 | assertEq(t2[9] instanceof SharedArrayBuffer, true); 79 | assertEq(t2[10] instanceof SharedArrayBuffer, true); 80 | assertEq(t2[11] instanceof Int32Array, true); 81 | assertEq(t2[12] instanceof Float64Array, true); 82 | assertEq(t2[8], t2[9]); 83 | assertEq(t2[8].byteLength, t2[8].byteLength); 84 | 85 | assertEq(t2[11].buffer, t2[8]); 86 | assertEq(t2[12].buffer, t2[10]); 87 | 88 | assertEq(t2[13] instanceof ArrayBuffer, true); 89 | assertEq(t2[13].byteLength, ab.byteLength); 90 | var tmp2 = new Uint8Array(ab); 91 | for ( var i=0 ; i < tmp.length ; i++ ) 92 | assertEq(tmp2[i], i); 93 | 94 | assertEq(t2[14] instanceof Int32Array, true); 95 | assertEq(t2[14].length, i32a.length); 96 | for ( var i=0 ; i < i32a.length ; i++ ) 97 | assertEq(t2[14][i], i32a[i]); 98 | 99 | assertEq(t2[15] instanceof Uint8Array, true); 100 | assertEq(t2[15].length, u8a.length); 101 | for ( var i=0 ; i < u8a.length ; i++ ) 102 | assertEq(t2[15][i], u8a[i]); 103 | 104 | assertEq(Array.isArray(t2[16]), true); 105 | assertEq(t2[16].length, a0.length); 106 | assertEq(Array.isArray(t2[16][2]), true); 107 | assertEq(t2[16][2].length, a0[2].length); 108 | assertEq(3 in t2[16], false); 109 | 110 | for ( var k in o0 ) { 111 | assertEq(t2[17].hasOwnProperty(k), true); 112 | } 113 | 114 | assertEq(typeof t2[18], 'function'); 115 | assertEq(t2[18](10), 55); // fib(10) => 55 116 | 117 | print("Done"); 118 | -------------------------------------------------------------------------------- /test/scrool.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Simple output abstraction. Call 'msg(s)' to print s in a message view. 6 | 7 | document.write(""); 8 | const scrool = document.getElementById("scrool"); 9 | 10 | function msg(s) { 11 | var d = document.createElement("div"); 12 | d.appendChild(document.createTextNode(s)); 13 | scrool.appendChild(d); 14 | } 15 | -------------------------------------------------------------------------------- /test/synchronic-shelltest.js: -------------------------------------------------------------------------------- 1 | load("../src/synchronic.js"); 2 | 3 | // Basic tests 4 | 5 | var sab = new SharedArrayBuffer(4096); 6 | 7 | // Assume: SynchronicT.BYTE_ALIGNMENT <= 16 for all T. 8 | 9 | var s = new SynchronicInt32(sab, 16, true); 10 | assertEq(s.load(), 0); 11 | s.store(37); 12 | assertEq(s.load(), 37); 13 | s.store(-42); 14 | assertEq(s.load(), -42); 15 | assertEq(s.add(5), -42); 16 | assertEq(s.and(15), -37); // 0xff..db 17 | assertEq(s.load(), 11); 18 | 19 | setSharedArrayBuffer(sab); 20 | 21 | evalInWorker(` 22 | load("../src/synchronic.js"); 23 | var sab = getSharedArrayBuffer(); 24 | var s = new SynchronicInt32(sab, 16); 25 | assertEq(s.load(), 11); 26 | assertEq(s.loadWhenEqual(11), 11); 27 | var then = Date.now(); 28 | assertEq(s.loadWhenEqual(12), 12); 29 | print("Waited (A) " + (Date.now() - then) + " (should be approx 1000ms)"); 30 | sleep(1); 31 | s.store(13); 32 | `); 33 | 34 | sleep(1); 35 | s.store(12); 36 | 37 | var then = Date.now(); 38 | assertEq(s.loadWhenEqual(13), 13); 39 | print("Waited (B) " + (Date.now() - then) + " (should be approx 1000ms)"); 40 | 41 | var s = new SynchronicInt8(sab, 32, true); 42 | 43 | evalInWorker(` 44 | load("../src/synchronic.js"); 45 | var sab = getSharedArrayBuffer(); 46 | var s = new SynchronicInt8(sab, 32); 47 | var then = Date.now(); 48 | s.expectUpdate(0, 500); // Should timeout before value is set 49 | assertEq(s.load(), 0); // Ergo value should be unchanged 50 | print("Waited (C) " + (Date.now() - then) + " (should be approx 500ms)"); 51 | var then = Date.now(); 52 | assertEq(s.loadWhenEqual(-8), -8); 53 | print("Waited (D) " + (Date.now() - then) + " (should be approx 500ms)"); 54 | `); 55 | 56 | sleep(1); 57 | s.store(-8); 58 | sleep(1); 59 | 60 | // Ditto float 61 | 62 | print("Float32"); 63 | 64 | var s = new SynchronicFloat32(sab, 48, true); 65 | assertEq(s.load(), 0); 66 | s.store(37.5); 67 | assertEq(s.load(), 37.5); 68 | s.store(-42.5); 69 | assertEq(s.load(), -42.5); 70 | assertEq(s.add(5), -42.5); 71 | assertEq(s.load(), -37.5); 72 | 73 | setSharedArrayBuffer(sab); 74 | 75 | evalInWorker(` 76 | load("../src/synchronic.js"); 77 | var sab = getSharedArrayBuffer(); 78 | var s = new SynchronicFloat32(sab, 48); 79 | assertEq(s.load(), -37.5); 80 | assertEq(s.loadWhenEqual(-37.5), -37.5); 81 | var then = Date.now(); 82 | assertEq(s.loadWhenEqual(12.5), 12.5); 83 | print("Waited (A) " + (Date.now() - then) + " (should be approx 1000ms)"); 84 | sleep(1); 85 | s.store(13.5); 86 | `); 87 | 88 | sleep(1); 89 | s.store(12.5); 90 | 91 | var then = Date.now(); 92 | assertEq(s.loadWhenEqual(13.5), 13.5); 93 | print("Waited (B) " + (Date.now() - then) + " (should be approx 1000ms)"); 94 | 95 | print("Float64"); 96 | 97 | memset(sab, 48, 0, SynchronicFloat64.BYTES_PER_ELEMENT); 98 | 99 | var s = new SynchronicFloat64(sab, 48, true); 100 | assertEq(s.load(), 0); 101 | s.store(37.5); 102 | assertEq(s.load(), 37.5); 103 | s.store(-42.5); 104 | assertEq(s.load(), -42.5); 105 | assertEq(s.add(5), -42.5); 106 | assertEq(s.load(), -37.5); 107 | 108 | setSharedArrayBuffer(sab); 109 | 110 | evalInWorker(` 111 | load("../src/synchronic.js"); 112 | var sab = getSharedArrayBuffer(); 113 | var s = new SynchronicFloat64(sab, 48); 114 | assertEq(s.load(), -37.5); 115 | assertEq(s.loadWhenEqual(-37.5), -37.5); 116 | var then = Date.now(); 117 | assertEq(s.loadWhenEqual(12.5), 12.5); 118 | print("Waited (A) " + (Date.now() - then) + " (should be approx 1000ms)"); 119 | sleep(1); 120 | s.store(13.5); 121 | `); 122 | 123 | sleep(1); 124 | s.store(12.5); 125 | 126 | var then = Date.now(); 127 | assertEq(s.loadWhenEqual(13.5), 13.5); 128 | print("Waited (B) " + (Date.now() - then) + " (should be approx 1000ms)"); 129 | 130 | function memset(sab, offset, val, len) { 131 | if (len == 0) 132 | return; 133 | var mem = new Int8Array(sab, offset, len); 134 | for ( var i=0 ; i < len-1 ; i++ ) 135 | mem[i] = val; 136 | // Publish those values 137 | Atomics.store(mem, len-1, val); 138 | } 139 | -------------------------------------------------------------------------------- /test/test-asymmetric-barrier-master.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Test the MasterBarrier/WorkerBarrier type. 6 | // 2015-01-20 / lhansen@mozilla.com 7 | // 8 | // Create K workers that, on each iteration, read an addend from 9 | // shared memory and add it to the elements in their segment of a 10 | // shared array, and then enter a barrier. 11 | // 12 | // The addend is multiplied by PI each iteration so a worker that 13 | // just races ahead is unlikely to compute the correct result. 14 | 15 | const numWorkers = 3; 16 | const segmentSize = 1000; 17 | const bufSize = segmentSize*numWorkers; 18 | const barrierID = 1337; 19 | const numIter = 3; 20 | 21 | var alloc = 0; 22 | var bufIdx = alloc/8; // Index in dab 23 | alloc += 8*bufSize; 24 | var addendIdx = alloc/8; // Index in dab 25 | alloc += 8; 26 | var barrierIdx = alloc/4; // Index in iab 27 | alloc += MasterBarrier.NUMINTS*4; 28 | 29 | var iter = 0; 30 | var addend = 1; 31 | var expected = 0; 32 | 33 | const sab = new SharedArrayBuffer(alloc); 34 | const iab = new Int32Array(sab); 35 | const dab = new Float64Array(sab); 36 | const barrier = new MasterBarrier(iab, barrierIdx, barrierID, numWorkers, barrierReady); 37 | 38 | function runTest() { 39 | for ( var id=0 ; id < numWorkers ; id++ ) { 40 | var w = new Worker("test-asymmetric-barrier-worker.js"); 41 | MasterBarrier.addWorker(w); 42 | w.addEventListener("message", function (ev) { msg(String(ev.data)) }); 43 | w.postMessage(["setup", sab, numIter, barrierIdx, barrierID, addendIdx, segmentSize*id, segmentSize]); 44 | } 45 | } 46 | 47 | function barrierReady() { 48 | if (iter++ < numIter) { 49 | if (iter > 1) 50 | addend *= Math.PI; 51 | dab[addendIdx] = addend; 52 | expected += addend; 53 | barrier.release(); 54 | } 55 | else { 56 | msg("Checking " + numWorkers*segmentSize + " elements"); 57 | for ( var i=0 ; i < numWorkers*segmentSize ; i++ ) 58 | if (dab[i] != expected) 59 | msg("Failed at element " + i + ": " + dab[i] + " " + expected); 60 | msg("done: master"); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/test-asymmetric-barrier-worker.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // 2015-01-12 / lhansen@mozilla.com 6 | 7 | importScripts("../util/shim.js", 8 | "../src/message.js", 9 | "../src/asymmetric-barrier.js"); 10 | 11 | dispatchMessage(self, "setup", function (data) { 12 | var [_, sab, numIter, barrierIdx, barrierID, addendIdx, segmentBase, segmentSize] = data; 13 | var iab = new Int32Array(sab); 14 | var dab = new Float64Array(sab); 15 | var barrier = new WorkerBarrier(iab, barrierIdx, barrierID); 16 | 17 | postMessage([numIter, barrierIdx, barrierID, addendIdx, segmentBase, segmentSize].join(" ")); 18 | for ( var i=0 ; i < numIter ; i++ ) { 19 | barrier.enter(); 20 | var addend = dab[addendIdx]; 21 | for ( var j=0; j < segmentSize ; j++ ) 22 | dab[segmentBase + j] += addend; 23 | } 24 | 25 | postMessage("done " + segmentBase); 26 | barrier.enter(); 27 | }); 28 | -------------------------------------------------------------------------------- /test/test-asymmetric-barrier.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/test-asymmetric-futex-master.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Test the MasterFutex/WorkerFutex type. 6 | // 2015-10-02 / lhansen@mozilla.com 7 | 8 | // TODO: multiple locations 9 | // TODO: multiple arrays 10 | // TODO: also see the -worker code 11 | 12 | var sab = new SharedArrayBuffer(100); 13 | var iab = new Int32Array(sab); 14 | var id = 0; 15 | var mf = new MasterFutex(iab, id); 16 | var testloc = 10; // iab[10] and iab[11] are reserved for a futex loc 17 | 18 | var w = new Worker("test-asymmetric-futex-worker.js"); 19 | 20 | w.onmessage = function (ev) { 21 | if (MasterFutex.dispatch(ev)) 22 | return; 23 | console.log(ev.data); 24 | } 25 | w.postMessage(["start", sab, id, testloc]); 26 | 27 | var then = new Date(); 28 | 29 | // Each waiting callback represents a separate thread of control in the master, really. 30 | // Waiters will be woken in order; meanwhile, the timeout will fire because that represents 31 | // another thread of control. 32 | // 33 | // Notice these are all waiting on the same location, and some simultaneously. 34 | 35 | mf.wait(testloc, 0, function (result) { 36 | console.log("Awoken 1 with result " + result + " (should be 0) after " + (new Date() - then) + " (should be just over 1000)"); 37 | 38 | // Awoken 2 and 3 will come a second after Awoken 1, so the timeout should come in between them 39 | setTimeout(function () { 40 | console.log("Should fire before awoken 2"); 41 | }, 500); 42 | 43 | // This will be woken after the two below because it ends up behind them in the queue 44 | mf.wait(testloc, 0, function (result) { 45 | console.log("Awoken 4 with result " + result + " (should be 0) after " + (new Date() - then) + " (should be just over 3000)"); 46 | }); 47 | }); 48 | 49 | // These two are woken together 50 | mf.wait(testloc, 0, function (result) { 51 | console.log("Awoken 2 with result " + result + " (should be 0) after " + (new Date() - then) + " (should be just over 2000)"); 52 | }); 53 | mf.wait(testloc, 0, function (result) { 54 | console.log("Awoken 3 with result " + result + " (should be 0) after " + (new Date() - then) + " (should be just over 2000)"); 55 | }); 56 | 57 | // Awoken 1 should come after a second so this should fire first. 58 | setTimeout(function () { 59 | console.log("Should fire before awoken 1"); 60 | }, 500); 61 | -------------------------------------------------------------------------------- /test/test-asymmetric-futex-worker.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | importScripts("../util/shim.js", "../src/asymmetric-futex.js"); 6 | 7 | onmessage = function (ev) { 8 | var [tag, sab, id, testloc] = ev.data; 9 | var iab = new Int32Array(sab); 10 | var wf = new WorkerFutex(iab, id); 11 | 12 | // TODO: out-of-range wake counts 13 | // TODO: also see the -master code 14 | 15 | Atomics.wait(iab, testloc, 0, 1000); 16 | wf.wake(testloc, 1); 17 | 18 | Atomics.wait(iab, testloc, 0, 1000); 19 | wf.wake(testloc, 2); 20 | 21 | Atomics.wait(iab, testloc, 0, 1000); 22 | wf.wake(testloc, 1); 23 | } 24 | -------------------------------------------------------------------------------- /test/test-asymmetric-futex.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/test-asymmetric-intqueue-m2w-master.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Master producer, worker consumers. 6 | // 7 | // The data are 4+n-word bundles: iter, n, fib(n), fib(n-3), 0, 1, ..., n-1 8 | 9 | // The worker can compute fib(n-3) from n and compare. 10 | 11 | const numWorkers = 4; 12 | const iterations = 200; 13 | 14 | const alloc = 2048; 15 | 16 | const sab = new SharedArrayBuffer(alloc); 17 | 18 | const MPIQ = new MasterProducerIntQueue(sab, 0, alloc, true); 19 | 20 | for ( let i=1 ; i <= numWorkers ; i++ ) { 21 | let w = new Worker("test-asymmetric-intqueue-m2w-worker.js"); 22 | w.postMessage(["init", i, sab, 0, alloc, iterations]); 23 | w.onmessage = handleMsg; 24 | } 25 | 26 | let numReceived = 0; 27 | let received = []; 28 | 29 | function handleMsg(ev) { 30 | if (AsymmetricSynchronic.filterEvent(ev)) 31 | return; 32 | if (Array.isArray(ev.data) && ev.data[0] === "phase1") { 33 | msg(ev.data.toSource()); 34 | let data = ev.data[2]; 35 | let id = ev.data[1]; 36 | for ( let i=0 ; i < data.length ; i++ ) { 37 | if (typeof received[data[i]] == "number") 38 | console.log("(1) Duplicate item: " + data[i]); 39 | received[data[i]] = id; 40 | } 41 | if (++numReceived < numWorkers) 42 | return; 43 | for ( let i=0 ; i < iterations*2 ; i++ ) { 44 | if (typeof received[i] != "number") 45 | console.log("(1) Missing item: " + i); 46 | } 47 | console.log("Phase 1 finished"); 48 | return; 49 | } 50 | msg(ev.data); 51 | } 52 | 53 | let iter = 0; 54 | let sentinels = 0; 55 | let phase = 1; 56 | 57 | function runTest() { 58 | phase1(); 59 | } 60 | 61 | let ds = []; 62 | 63 | function phase1() { 64 | if (iter == iterations) { 65 | if (phase == 1) { 66 | iter = 0; 67 | phase = 2; 68 | return phase1(); 69 | } 70 | if (sentinels == numWorkers) 71 | return; 72 | ++sentinels; 73 | return putItem([0, 0, 0, 0], phase1); 74 | } 75 | iter++; 76 | let n = (iter % 8) + 3; 77 | let d; 78 | if (phase == 1) { 79 | d = [iter-1, n, fib(n), fib(n-3)]; 80 | for ( let i=0 ; i < n ; i++ ) 81 | d.push(i); 82 | ds.push(d); 83 | } 84 | else { 85 | d = ds[iter-1]; 86 | d[0] += iterations; 87 | } 88 | return putItem(d, phase1); 89 | } 90 | 91 | function putItem(item, k) { 92 | if (MPIQ.putOrFail(item)) { 93 | //console.log("Item was put: " + item); 94 | return k(); 95 | } 96 | //console.log("Item was delayed: " + item); 97 | return MPIQ.callWhenCanPut(item.length, () => putItem(item, k)); 98 | } 99 | 100 | function fib(n) { 101 | if (n < 2) 102 | return n; 103 | return fib(n-1) + fib(n-2); 104 | } 105 | 106 | -------------------------------------------------------------------------------- /test/test-asymmetric-intqueue-m2w-worker.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | importScripts("../util/shim.js", 6 | "../src/asymmetric-synchronic.js", 7 | "../src/asymmetric-intqueue.js"); 8 | 9 | onmessage = function (ev) { 10 | let [_, myId, sab, offset, alloc, iterations] = ev.data; 11 | let MPIQ = new MasterProducerIntQueue(sab, offset, alloc, false); 12 | 13 | let received = []; 14 | for (;;) { 15 | let item = MPIQ.take(); 16 | //console.log("Item was received: " + item); 17 | if (item[1] === 0) 18 | break; 19 | if (item.length != item[1] + 4) { 20 | console.log("Bad item: " + item); 21 | break; 22 | } 23 | let [iter, n, fibn, fibn3] = item; 24 | if (fibn3 != fib(n-3)) { 25 | console.log("Bad data: " + item); 26 | break; 27 | } 28 | received.push(iter); 29 | } 30 | postMessage(["phase1", myId, received]); 31 | } 32 | 33 | function fib(n) { 34 | if (n < 2) 35 | return n; 36 | return fib(n-1) + fib(n-2); 37 | } 38 | -------------------------------------------------------------------------------- /test/test-asymmetric-intqueue-m2w.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/test-asymmetric-synchronic-master.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // STALE 6 | 7 | // Test 1. 8 | // 9 | // This creates some workers and then sets up SynchronicMasterUpdates 10 | // and SynchronicWorkerUpdates objects as a communication channel. 11 | // 12 | // Initially the workers all listen on the SynchronicMasterUpdates. 13 | // The values transmitted on this channel will be worker IDs. When a 14 | // worker observes its own ID on the channel, it goes off and does 15 | // some work and then writes its ID to the SynchronicWorkerUpdates 16 | // channel and goes back to waiting. The master will listen on that 17 | // channel and when it sees that a worker has completed its task it 18 | // will send the next worker ID to the workers. And so on. 19 | // 20 | // This more or less just tests basic functionality. All the workers 21 | // will awake when the master writes a value and will then need to 22 | // check the value and to listen again if the value is for some other 23 | // worker. 24 | 25 | "use strict"; 26 | 27 | const DEBUG = true; 28 | 29 | const numWorkers = 3; 30 | const iterations = 10; 31 | 32 | const alloc = 256; 33 | const asOffset = 0; 34 | 35 | const sab = new SharedArrayBuffer(alloc); 36 | 37 | const AS = new AsymmetricSynchronic(sab, asOffset, true); 38 | 39 | for ( let i=1 ; i <= numWorkers ; i++ ) { 40 | let w = new Worker("test-asymmetric-synchronic-worker.js"); 41 | w.postMessage(["init", i, sab, asOffset]); 42 | w.onmessage = handleMsg; 43 | } 44 | 45 | function handleMsg(ev) { 46 | if (AsymmetricSynchronic.filterEvent(ev)) 47 | return; 48 | msg(ev.data); 49 | } 50 | 51 | let iter = 0; 52 | let next = 0; 53 | 54 | function runTest() { 55 | nextWorker(); 56 | } 57 | 58 | function nextWorker() { 59 | if (iter == iterations) 60 | return; 61 | iter++; 62 | let last = Math.abs(AS.load()); 63 | if (DEBUG) 64 | msg("nextWorker invoked, last=" + last); // 0 the first time, then 1, 2, 3, 1, 2, 3, ... 65 | let wanted = next + 1; 66 | AS.store(wanted); 67 | next = (next + 1) % numWorkers; 68 | AS.callWhenEquals(-wanted, 69 | function handle() { 70 | let v = 0; 71 | if ((v = AS.load()) != -wanted) { 72 | msg("Callback invoked but with unwanted value " + v + ", wanted " + -wanted); 73 | AS.callWhenUpdated(last, handle); 74 | } 75 | else { 76 | msg("Callback invoked with wanted value"); 77 | nextWorker(); 78 | } 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /test/test-asymmetric-synchronic-worker.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | "use strict"; 6 | 7 | importScripts("../util/shim.js", "../src/asymmetric-synchronic.js"); 8 | 9 | var DEBUG = false; 10 | 11 | onmessage = function (ev) { 12 | let d = ev.data; 13 | let myId = d[1]; 14 | let sab = d[2]; 15 | let asOffset = d[3]; 16 | let AS = new AsymmetricSynchronic(sab, asOffset, false); 17 | let v = 0; 18 | let results = []; 19 | let iter = 0; 20 | for (;;) { 21 | AS.waitUntilEquals(myId); 22 | v = AS.load(); 23 | if (v != myId) { 24 | ++iter; 25 | console.log("Worker " + myId + " saw " + v + " but wanted " + myId + "; retrying " + iter); 26 | if (iter > 100) { 27 | console.log("Too many retries; bailing out"); 28 | break; 29 | } 30 | continue; 31 | } 32 | iter = 0; 33 | results.push(fib(25+v)); 34 | if (DEBUG) 35 | console.log("Storing -myId: " + -myId); 36 | AS.store(-myId); 37 | } 38 | } 39 | 40 | function fib(n) { 41 | if (n < 2) 42 | return n; 43 | return fib(n-1) + fib(n-2); 44 | } 45 | 46 | -------------------------------------------------------------------------------- /test/test-asymmetric-synchronic.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/test-barrier-master.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Test the Barrier type. 6 | // 7 | // Create K workers that each add their ID to the elements in the 8 | // range of a shared array, and then enter a barrier. On the next 9 | // round they add their ID to the next range of the shared array, and 10 | // so on, until all have done all the ranges. The shared array's 11 | // elements should now all have the sum of all the IDs. 12 | // 13 | // The main thread participates as one of the computing agents. 14 | // 15 | // In order to catch races - where the barrier is not obeyed - we 16 | // double the number of barriers, and for each segment first invert 17 | // the value, do a barrier, then invert it again, then add the value. 18 | // 19 | // (This still feels like a fairly weak test.) 20 | 21 | var numWorkers = 3; 22 | var numSegments = numWorkers + 1; 23 | var segmentSize = 1000; 24 | var bufIdx = 0; 25 | var bufSize = segmentSize*numSegments; 26 | var barrierIdx = bufIdx+bufSize; 27 | var iabSize = barrierIdx + Barrier.NUMINTS; 28 | 29 | var iab = new Int32Array(new SharedArrayBuffer(iabSize*Int32Array.BYTES_PER_ELEMENT)); 30 | Barrier.initialize(iab, barrierIdx, numSegments); 31 | var barrier = new Barrier(iab, barrierIdx); 32 | 33 | function runTest() { 34 | var readies = 0; 35 | for ( var id=0 ; id < numWorkers ; id++ ) { 36 | var w = new Worker("test-barrier-worker.js"); 37 | w.onmessage = 38 | function (ev) { 39 | msg(String(ev.data)); 40 | if (ev.data.indexOf("ready ") == 0) { 41 | ++readies; 42 | if (readies == numWorkers) 43 | setTimeout(worker, 0); 44 | } 45 | }; 46 | // Workers have ID 1..numWorkers, master has ID numWorkers+1 47 | w.postMessage([iab.buffer, bufIdx, bufSize, barrierIdx, numSegments, segmentSize, id+1]); 48 | } 49 | } 50 | 51 | function worker() { 52 | msg("running: master"); 53 | var myID = numWorkers+1; 54 | 55 | // Note this code assumes bufIdx == 0 56 | var seg = (myID - 1); 57 | for ( var i=0 ; i < numSegments ; i++ ) { 58 | for ( var j=0 ; j < segmentSize ; j++ ) 59 | iab[seg*segmentSize + j] = ~iab[seg*segmentSize + j]; 60 | barrier.enter(); 61 | for ( var j=0 ; j < segmentSize ; j++ ) { 62 | iab[seg*segmentSize + j] = ~iab[seg*segmentSize + j]; 63 | iab[seg*segmentSize + j] += myID; 64 | } 65 | seg = (seg+1) % numSegments; 66 | barrier.enter(); 67 | } 68 | 69 | msg("Checking " + numSegments*segmentSize + " elements"); 70 | var expect = (numSegments*(numSegments+1)/2)|0; 71 | for ( var i=0 ; i < numSegments*segmentSize ; i++ ) 72 | if ((iab[i]|0) != expect) 73 | msg("Failed at element " + i + ": " + (iab[i]|0) + " " + expect); 74 | msg("done: master"); 75 | } 76 | -------------------------------------------------------------------------------- /test/test-barrier-worker.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // 2015-01-12 / lhansen@mozilla.com 6 | 7 | importScripts("../src/barrier.js"); 8 | 9 | onmessage = 10 | function (ev) { 11 | var [sab, bufIdx, bufSize, barrierIdx, numSegments, segmentSize, myID] = ev.data; 12 | var iab = new Int32Array(sab); 13 | var barrier = new Barrier(iab, barrierIdx); 14 | 15 | postMessage("ready " + myID); 16 | 17 | // Note this code assumes bufIdx == 0 18 | var seg = (myID - 1); 19 | for ( var i=0 ; i < numSegments ; i++ ) { 20 | for ( var j=0 ; j < segmentSize ; j++ ) 21 | iab[seg*segmentSize + j] = ~iab[seg*segmentSize + j]; 22 | barrier.enter(); 23 | for ( var j=0 ; j < segmentSize ; j++ ) { 24 | iab[seg*segmentSize + j] = ~iab[seg*segmentSize + j]; 25 | iab[seg*segmentSize + j] += myID; 26 | } 27 | seg = (seg+1) % numSegments; 28 | barrier.enter(); 29 | } 30 | 31 | postMessage("done " + myID); 32 | }; 33 | -------------------------------------------------------------------------------- /test/test-barrier.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | This test sends a simple JS Object { item: n }
where
12 | n
is a nonnegative integer back and forth between a main thread and
13 | a worker using postMessage and normal onmessage handlers. The value
14 | of n
is incremented before each send. The object is sent 100,000
15 | times. When the run is over the number of messages per second is
16 | printed below.
The test can run from a file:
URL in Firefox and Safari,
19 | but because it uses a worker you must load it via a webserver in
20 | Chrome. If you've cloned the github repo containing this test, you
21 | can serve the files in this directory by running nodejs ../bench/static-server.js
and then loading http://localhost:8888/test-postmsg.html
.
22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/test-sendint-master.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // This is the "simplest possible" message passing benchmark: it has 6 | // one integer location for data and uses one synchronic to ping-pong 7 | // a critical section for that datum between threads. 8 | 9 | // On my MacBook Pro this currently gets about 6,250,000 msgs/sec, 10 | // which sure beats anything possible with postMessage. 11 | 12 | var iterations = 500000; 13 | var bufSize = 1024; // Should be divisible by 2 and "large enough" 14 | 15 | var w = new Worker("test-sendint-worker.js"); 16 | var sab = new SharedArrayBuffer(bufSize); 17 | 18 | // Setup our state first. 19 | 20 | var s = new SynchronicInt32(sab, 0); 21 | var locIdx = 512; 22 | 23 | // Kick off the worker and wait for a message that it is ready. 24 | 25 | w.onmessage = workerReady; 26 | w.postMessage([sab, 0, 512, iterations]); 27 | 28 | console.log("Master waiting"); 29 | 30 | function workerReady(ev) { 31 | var iab = new Int32Array(sab, locIdx, 1); 32 | var start = Date.now(); 33 | 34 | for ( var i=0 ; i < iterations ; i++ ) { 35 | iab[0]++; 36 | var old = s.add(1); 37 | s.expectUpdate(old+1, Number.POSITIVE_INFINITY); 38 | } 39 | 40 | var end = Date.now(); 41 | 42 | console.log("Should be " + iterations*2 + ": " + iab[0]); 43 | console.log(Math.round(1000 * (2*iterations) / (end - start)) + " messages/s"); 44 | } 45 | -------------------------------------------------------------------------------- /test/test-sendint-worker.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | importScripts("../util/shim.js", "../src/synchronic.js"); 6 | 7 | onmessage = 8 | function (ev) { 9 | var [sab, sabIdx, locIdx, iterations] = ev.data; 10 | 11 | // Initialize our state 12 | 13 | var s = new SynchronicInt32(sab, 0); 14 | var iab = new Int32Array(sab, locIdx, 1); 15 | 16 | // Let the master know we're ready to go 17 | 18 | postMessage("ready"); 19 | 20 | var x = 0; 21 | for ( var i=0 ; i < iterations ; i++ ) { 22 | s.expectUpdate(x, Number.POSITIVE_INFINITY); 23 | iab[0]++; 24 | x = s.add(1)+1; 25 | } 26 | 27 | console.log("Worker exiting"); 28 | }; 29 | -------------------------------------------------------------------------------- /test/test-sendint.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/test-sendmsg-master.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Data points (Nightly, May 2015): 6 | // 7 | // - Shared-memory channels are about 5x faster than postMessage 8 | // channels (210K msg/s vs 41K msg/s) when sending just one integer 9 | // back and forth. 10 | // 11 | // - Shared-memory channels increase the advantage to 6x when sending 12 | // objects with an integer field (160K vs 25K). 13 | 14 | var iterations = 100000; 15 | var bufSize = 8192; // Should be divisible by 2 and "large enough" 16 | // (8K is much more than needed for this test) 17 | 18 | var w = new Worker("test-sendmsg-worker.js"); 19 | var sab = new SharedArrayBuffer(bufSize); 20 | 21 | // Setup our state first. 22 | 23 | var s = new ChannelSender(sab, 0, bufSize/2); 24 | var r = new ChannelReceiver(sab, bufSize/2, bufSize/2); 25 | 26 | // Kick off the worker and wait for a message that it is ready. 27 | 28 | w.onmessage = workerReady; 29 | w.postMessage([sab, iterations, 0, bufSize/2, bufSize/2, bufSize/2]); 30 | 31 | console.log("Master waiting"); 32 | 33 | function workerReady(ev) { 34 | var start = Date.now(); 35 | 36 | var c = {item:0}; 37 | for ( var i=0 ; i < iterations ; i++ ) { 38 | s.send(c); 39 | c = r.receive(); 40 | } 41 | 42 | var end = Date.now(); 43 | 44 | console.log("Should be " + iterations + ": " + c.item); 45 | console.log(Math.round(1000 * (2*iterations) / (end - start)) + " messages/s"); 46 | } 47 | -------------------------------------------------------------------------------- /test/test-sendmsg-sibling1.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | importScripts("../src/arena.js", 6 | "../src/synchronic.js", 7 | "../src/marshaler.js", 8 | "../src/intqueue.js", 9 | "../src/channel.js"); 10 | 11 | onmessage = 12 | function (ev) { 13 | var [_, id, sab, iterations] = ev.data; 14 | console.log("setting up " + id); 15 | s = new ChannelSender(sab, 0, 4096); 16 | r = new ChannelReceiver(sab, 4096, 4096); 17 | onmessage = 18 | function firstSibling(ev) { 19 | console.log("running " + id); 20 | var c = {item:0}; 21 | var start; 22 | for ( var i=0 ; i < iterations ; i++ ) { 23 | s.send(c); 24 | if (i == 0) 25 | start = Date.now(); 26 | c = r.receive(); 27 | } 28 | var time = (Date.now() - start); 29 | console.log("Time: " + time + "ms, perf=" + Math.round((iterations*2) / (time / 1000)) + " msg/s"); 30 | console.log("worker " + id + " exiting"); 31 | }; 32 | postMessage("ready"); 33 | }; 34 | -------------------------------------------------------------------------------- /test/test-sendmsg-sibling2.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | importScripts("../src/arena.js", 6 | "../src/synchronic.js", 7 | "../src/marshaler.js", 8 | "../src/intqueue.js", 9 | "../src/channel.js"); 10 | 11 | onmessage = 12 | function (ev) { 13 | var [_, id, sab, iterations] = ev.data; 14 | console.log("setting up " + id); 15 | var r = new ChannelReceiver(sab, 0, 4096); 16 | var s = new ChannelSender(sab, 4096, 4096); 17 | onmessage = 18 | function (ev) { 19 | console.log("running " + id); 20 | for ( var i=0 ; i < iterations ; i++ ) { 21 | var c = r.receive(); 22 | c.item++; 23 | s.send(c); 24 | } 25 | console.log("worker " + id + " exiting"); 26 | }; 27 | postMessage("ready"); 28 | }; 29 | -------------------------------------------------------------------------------- /test/test-sendmsg-siblings.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/test-sendmsg-siblings.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Create two sibling workers, who share some memory that they use for 6 | // communication. 7 | 8 | // This has the same performance as the master-to-worker case: about 9 | // 165000 msgs/sec (objects with a single field) on my late-2013 10 | // MacBook Pro. 11 | 12 | var iterations = 100000; 13 | 14 | var w1 = new Worker("test-sendmsg-sibling1.js"); 15 | var w2 = new Worker("test-sendmsg-sibling2.js"); 16 | var sab = new SharedArrayBuffer(8192); 17 | 18 | w1.onmessage = workerReady; 19 | w2.onmessage = workerReady; 20 | 21 | w1.postMessage(["setup", 1, sab, iterations]); 22 | w2.postMessage(["setup", 2, sab, iterations]); 23 | 24 | var waiting = 2; 25 | 26 | function workerReady(ev) { 27 | --waiting; 28 | if (waiting > 0) 29 | return; 30 | 31 | w1.postMessage(["go"]); 32 | w2.postMessage(["go"]); 33 | } 34 | 35 | function runTest() {} 36 | -------------------------------------------------------------------------------- /test/test-sendmsg-worker.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | importScripts("../src/arena.js", 6 | "../src/synchronic.js", 7 | "../src/marshaler.js", 8 | "../src/intqueue.js", 9 | "../src/channel.js"); 10 | 11 | onmessage = 12 | function (ev) { 13 | var [sab, iterations, recvIdx, recvLength, sendIdx, sendLength] = ev.data; 14 | 15 | // Initialize our state 16 | 17 | var r = new ChannelReceiver(sab, recvIdx, recvLength); 18 | var s = new ChannelSender(sab, sendIdx, sendLength); 19 | 20 | // Let the master know we're ready to go 21 | 22 | postMessage("ready"); 23 | 24 | var c = {item:-1}; 25 | for ( var i=0 ; i < iterations ; i++ ) { 26 | c = r.receive(); 27 | c.item++; 28 | s.send(c); 29 | } 30 | 31 | console.log("Worker exiting"); 32 | }; 33 | -------------------------------------------------------------------------------- /test/test-sendmsg.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /util/asmjs.js: -------------------------------------------------------------------------------- 1 | function roundupAsmJSHeapLength(nbytes) { 2 | const sixteen = 16*1024*1024; 3 | if (nbytes < 65536) { 4 | // Must be at least 64KB 5 | nbytes = 65536; 6 | } 7 | if (nbytes < sixteen) { 8 | // Must be power of 2 9 | var k = 0; 10 | while (nbytes != 1) { 11 | if (nbytes & 1) 12 | nbytes += 1; 13 | k++; 14 | nbytes >>= 1; 15 | } 16 | nbytes <<= k; 17 | } 18 | else if (nbytes % sixteen) { 19 | // Must be multiple of 16M 20 | nbytes = (nbytes + (sixteen - 1)) & ~(sixteen - 1); 21 | } 22 | return nbytes; 23 | } 24 | -------------------------------------------------------------------------------- /util/canvas.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Utilities for simple manipulation of canvas elements. 6 | 7 | function canvasSetFromGrayscale(canvasElt, bytes, height, width) { 8 | canvasElt.height = height; 9 | canvasElt.width = width; 10 | var cx = canvasElt.getContext('2d'); 11 | var id = cx.createImageData(width, height); 12 | var data = new Int32Array(id.data.buffer, id.data.byteOffset, width*height); 13 | for ( var y=0 ; y < height ; y++ ) { 14 | for ( var x=0 ; x < width ; x++ ) { 15 | var v = bytes[y*width+x] & 255; 16 | data[y*width+x] = 0xFF000000 | (v << 16) | (v << 8) | v; 17 | } 18 | } 19 | cx.putImageData( id, 0, 0 ); 20 | } 21 | 22 | // Caching is not all that important I think, and of course we're holding on to stuff here, 23 | // but it helps when doing animations and for that case it's realistic to cache things. 24 | const cache = { element: null, cx: null, id: null, height: 0, width: 0 }; 25 | 26 | function canvasSetFromABGRBytes(canvasElt, bytes, height, width) { 27 | if (cache.element != canvasElt || cache.height != height || cache.width != width) { 28 | canvasElt.height = height; 29 | canvasElt.width = width; 30 | cache.element = canvasElt; 31 | cache.height = height; 32 | cache.width = width; 33 | cache.cx = canvasElt.getContext('2d'); 34 | cache.id = cache.cx.createImageData(width, height); 35 | } 36 | cache.id.data.set(bytes); 37 | cache.cx.putImageData( cache.id, 0, 0 ); 38 | } 39 | -------------------------------------------------------------------------------- /util/numWorkers.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // Determine the number of workers 6 | 7 | const defaultNumWorkers = 4; 8 | const numWorkers = 9 | (function () { 10 | if (!this.document || !document.location) 11 | return defaultNumWorkers; 12 | var param=String(document.location).match(/workers=(\d+)/); 13 | if (!param) 14 | return defaultNumWorkers; 15 | return parseInt(param[1]); 16 | })(); 17 | -------------------------------------------------------------------------------- /util/shim.js: -------------------------------------------------------------------------------- 1 | // Big API changes in March 2016: 2 | // 3 | // - Atomics.futexWait renamed as Atomics.wait 4 | // - Atomics.futexWake renamed as Atomics.wake 5 | // - Atomics.wake returns strings, not integers 6 | // - Atomics.OK removed 7 | // - Atomics.TIMEDOUT removed 8 | // - Atomics.NOTEQUAL removed 9 | // - Atomics.futexWakeOrRequeue removed 10 | // 11 | // This shim converts an older browser to the new system. 12 | 13 | if (Atomics.wait === undefined) { 14 | if (Atomics.OK === undefined) 15 | Atomics.wait = Atomics.futexWait; 16 | else 17 | Atomics.wait = (function (futexWait, OK, TIMEDOUT, NOTEQUAL) { 18 | return function (ia, idx, val, t) { 19 | switch (futexWait(ia, idx, val, t)) { 20 | case OK : return "ok"; 21 | case TIMEDOUT : return "timed-out"; 22 | case NOTEQUAL : return "not-equal"; 23 | } 24 | } 25 | })(Atomics.futexWait, Atomics.OK, Atomics.TIMEDOUT, Atomics.NOTEQUAL); 26 | } 27 | 28 | if (Atomics.wake === undefined) 29 | Atomics.wake = Atomics.futexWake; 30 | 31 | delete Atomics.OK; 32 | delete Atomics.TIMEDOUT; 33 | delete Atomics.NOTEQUAL; 34 | delete Atomics.futexWait; 35 | delete Atomics.futexWake; 36 | delete Atomics.futexWakeOrRequeue; 37 | --------------------------------------------------------------------------------