├── .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 |
 
16 |
17 |
18 | asm.js + simd (float32)
19 | asm.js (float32)
20 | plain js (float64)
21 |
22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /demo/mandelbrot-animation/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 | // We set up shared memory and a Barrier and distribute them to the 8 | // workers. The workers then enter the barrier. Once they've all 9 | // done so the master receives a callback, after which it distributes 10 | // animation parameters and releases the workers. After they have 11 | // computed they enter the barrier again; the master gets another 12 | // callback; and so on. The master signals that the work is finished 13 | // by passing distinguished terminal values for the animation 14 | // parameters. 15 | 16 | // Animation parameters 17 | 18 | const magFactor = 1.05; 19 | const maxIterations = 250; // Set to 1 for a single frame 20 | 21 | // The memory contains the height*width grid and extra shared space 22 | // for the barrier that is used to coordinate workers. 23 | 24 | const rawmem = new SharedArrayBuffer((height*width + MasterBarrier.NUMINTS)*4 + 1*8); 25 | const intmem = new Int32Array(rawmem, 0, height*width + MasterBarrier.NUMINTS); 26 | const flomem = new Float64Array(rawmem, (height*width + MasterBarrier.NUMINTS)*4, 1); 27 | const barrierLoc = (height*width); // Within intmem 28 | const magnificationLoc = 0; // Within flomem 29 | const barrierID = 1337; 30 | 31 | // Note, numWorkers is set by the .html document. 32 | 33 | const barrier = new MasterBarrier(intmem, barrierLoc, barrierID, numWorkers, barrierCallback); 34 | const sliceHeight = Math.ceil(height/numWorkers); 35 | 36 | for ( var i=0 ; i < numWorkers ; i++ ) { 37 | var w = new Worker("mandelbrot-worker.js"); 38 | MasterBarrier.addWorker(w); 39 | w.postMessage(["setup", 40 | rawmem, 41 | intmem.byteOffset, 42 | intmem.length, 43 | flomem.byteOffset, 44 | flomem.length, 45 | barrierID, 46 | barrierLoc, 47 | magnificationLoc, 48 | i*sliceHeight, 49 | (i == numWorkers-1 ? height : (i+1)*sliceHeight)]); 50 | } 51 | 52 | var magnification = 1; 53 | var iterations = 0; 54 | var timeBefore = 0; 55 | 56 | function barrierCallback() { 57 | if (!timeBefore) 58 | timeBefore = Date.now(); 59 | else { 60 | canvasSetFromABGRBytes(document.getElementById("mycanvas"), 61 | new Uint8Array(rawmem, 0, height*width*4), 62 | height, 63 | width); 64 | magnification *= magFactor; 65 | } 66 | 67 | if (iterations++ < maxIterations) 68 | flomem[magnificationLoc] = magnification; 69 | else { 70 | flomem[magnificationLoc] = (iterations++ < maxIterations) ? magnification : 0; 71 | var time = (Date.now() - timeBefore) 72 | document.getElementById("mystatus").textContent = 73 | "Number of workers: " + numWorkers + " Compute time: " + time + "ms Frames: " + maxIterations + " FPS: " + (maxIterations/(time/1000)); 74 | } 75 | 76 | barrier.release(); 77 | } 78 | -------------------------------------------------------------------------------- /demo/mandelbrot-animation/mandelbrot-parameters.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 | // Center the image at this location. 6 | const g_center_x = -0.743643887037158704752191506114774; 7 | const g_center_y = 0.131825904205311970493132056385139; 8 | 9 | // Pixel grid. (0,0) correspons to (bottom,left) 10 | const height = 480; 11 | const width = 640; 12 | -------------------------------------------------------------------------------- /demo/mandelbrot-animation/mandelbrot-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 | // See explanation in mandelbrot-master.js. 8 | 9 | importScripts("../../util/shim.js", 10 | "../../src/message.js", 11 | "../../src/asymmetric-barrier.js", 12 | "mandelbrot-parameters.js"); 13 | 14 | dispatchMessage(self, "setup", function (data) { 15 | var [_, sab, intByteOffset, intLength, floByteOffset, floLength, barrierID, barrierLoc, magnificationLoc, ybase, ylimit] = data; 16 | var intmem = new Int32Array(sab, intByteOffset, intLength); 17 | var flomem = new Float64Array(sab, floByteOffset, floLength); 18 | var barrier = new WorkerBarrier(intmem, barrierLoc, barrierID); 19 | 20 | barrier.enter(); 21 | while (flomem[magnificationLoc] != 0) { 22 | mandelbrot(intmem, ybase, ylimit, flomem[magnificationLoc]); 23 | barrier.enter(); 24 | } 25 | }); 26 | 27 | // Maximum iterations per pixel. 28 | const MAXIT = 200; 29 | 30 | // Colors are ABGR with A=255. 31 | const colors = [0xFFFF0700, 0xFF2a2aa5, 0xFFFFff00, 0xFFa19eff, 32 | 0xFF00eefd, 0xFF008000, 0xFFFAFEFE, 0xFF00FFBF]; 33 | 34 | // Compute a strip of pixels from ybase <= y < ylimit. 35 | function mandelbrot(mem, ybase, ylimit, magnification) { 36 | const g_top = g_center_y + 1/magnification; 37 | const g_bottom = g_center_y - 1/magnification; 38 | const g_left = g_center_x - width/height*1/magnification; 39 | const g_right = g_center_x + width/height*1/magnification; 40 | for ( var Py=ybase ; Py < ylimit ; Py++ ) { 41 | for ( var Px=0 ; Px < width ; Px++ ) { 42 | var x0 = g_left+(Px/width)*(g_right-g_left); 43 | var y0 = g_bottom+(Py/height)*(g_top-g_bottom); 44 | var x = 0.0; 45 | var y = 0.0; 46 | var it = 0; 47 | while (x*x + y*y < 4.0 && it < MAXIT) { 48 | var xtemp = x*x - y*y + x0; 49 | y = 2.0*x*y + y0; 50 | x = xtemp; 51 | it++; 52 | } 53 | mem[Py*width+Px] = it == MAXIT ? 0xFF000000 : colors[it & 7]; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /demo/mandelbrot-animation/mandelbrot.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /demo/mandelbrot-animation2/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.05; 10 | const maxIterations = 250; 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(4*(height*width*2 + MasterPar.NUMINTS)); 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 | 21 | // Note, numWorkers is set by the .html document. 22 | 23 | const Par = new MasterPar(memp, 0, numWorkers, "mandelbrot-worker.js", doMandelbrot); 24 | 25 | var magnification = 1; 26 | var iterations = 0; 27 | var mem = mem1; 28 | var timeBefore; 29 | 30 | function doMandelbrot() { 31 | Par.invoke(showMandelbrot, "mandelbrot", [[0,height], [0,width]], mem, magnification); 32 | } 33 | 34 | function showMandelbrot() { 35 | var memnow = mem; 36 | if (iterations == 0) 37 | timeBefore = Date.now(); 38 | if (iterations < maxIterations) { 39 | iterations++; 40 | magnification *= magFactor; 41 | mem = (memnow == mem1) ? mem2 : mem1; 42 | // Overlap display of this frame with computation of the next. 43 | doMandelbrot(); 44 | } 45 | else { 46 | var t = Date.now() - timeBefore; 47 | var fps = Math.round((iterations/(t/1000))*10)/10; 48 | document.getElementById('mystatus').innerHTML = "Number of workers: " + numWorkers + " Compute time: " + t + "ms FPS=" + fps; 49 | } 50 | canvasSetFromABGRBytes(document.getElementById("mycanvas"), 51 | new Uint8Array(rawmem, memnow.byteOffset, height*width*4), 52 | height, 53 | width); 54 | } 55 | -------------------------------------------------------------------------------- /demo/mandelbrot-animation2/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 | -------------------------------------------------------------------------------- /demo/mandelbrot-animation2/mandelbrot-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", 8 | "../../src/message.js", 9 | "../../src/asymmetric-barrier.js", 10 | "../../src/marshaler.js", 11 | "../../src/par.js", 12 | "mandelbrot-parameters.js"); 13 | 14 | const Par = new WorkerPar(); 15 | 16 | // Maximum iterations per pixel. 17 | const MAXIT = 200; 18 | 19 | // Colors are ABGR with A=255. 20 | const colors = [0xFFFF0700, 0xFF2a2aa5, 0xFFFFff00, 0xFFa19eff, 21 | 0xFF00eefd, 0xFF008000, 0xFFFAFEFE, 0xFF00FFBF]; 22 | 23 | // Compute a square of pixels into mem with y in [ybase, ylimit) 24 | // and x in [xbase, xlimit). 25 | 26 | function mandelbrot(ybase, ylimit, xbase, xlimit, mem, magnification) { 27 | const g_top = g_center_y + 1/magnification; 28 | const g_bottom = g_center_y - 1/magnification; 29 | const g_left = g_center_x - width/height*1/magnification; 30 | const g_right = g_center_x + width/height*1/magnification; 31 | for ( var Py=ybase ; Py < ylimit ; Py++ ) { 32 | for ( var Px=xbase ; Px < xlimit ; Px++ ) { 33 | var x0 = g_left+(Px/width)*(g_right-g_left); 34 | var y0 = g_bottom+(Py/height)*(g_top-g_bottom); 35 | var x = 0.0; 36 | var y = 0.0; 37 | var it = 0; 38 | while (x*x + y*y < 4.0 && it < MAXIT) { 39 | var xtemp = x*x - y*y + x0; 40 | y = 2.0*x*y + y0; 41 | x = xtemp; 42 | it++; 43 | } 44 | mem[Py*width+Px] = it == MAXIT ? 0xFF000000 : colors[it & 7]; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /demo/mandelbrot-animation2/mandelbrot.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /demo/ray-flatjs/Makefile: -------------------------------------------------------------------------------- 1 | .SUFFIXES: .flat_js .js 2 | 3 | SOURCES=ray-master.flat_js ray-common.flat_js ray-worker.flat_js 4 | TARGETS=ray-master.js ray-common.js ray-worker.js 5 | FJSC=../../../flatjs/fjsc.js 6 | 7 | # This is dodgy because types that are in ray-master will be visible 8 | # within ray-worker, which is not the intent. The correct way to do 9 | # this is probably to compile twice, with output to two artificial 10 | # files that merge the sources, a la TypeScript. Fix later. 11 | 12 | $(TARGETS): $(SOURCES) 13 | nodejs $(FJSC) $(SOURCES) 14 | -------------------------------------------------------------------------------- /demo/ray-flatjs/README.md: -------------------------------------------------------------------------------- 1 | Ray tracer - FlatJS parallel code. See comments in ray-master.flat_js. 2 | 3 | expected-output.ppm is the expected output with all features turned on. 4 | -------------------------------------------------------------------------------- /demo/ray-flatjs/expected-output.ppm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lars-t-hansen/parlib-simple/3dae87d76a86b21b0590f8f7f1abdf3a7be77ff2/demo/ray-flatjs/expected-output.ppm -------------------------------------------------------------------------------- /demo/ray-flatjs/ray-master.flat_js: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript -*- */ 2 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | * 7 | * Author: Lars T Hansen, lth@acm.org / lhansen@mozilla.com 8 | */ 9 | 10 | /* 11 | * Ray tracer, largely out of Shirley & Marschner 3rd Ed. 12 | * Traces a scene and writes to a canvas. 13 | * 14 | * This is written in FlatJS (github.com/lars-t-hansen/flatjs), 15 | * holding the scene graph in flat shared memory and rendering in 16 | * parallel into a flat shared array. The computation is otherwise 17 | * straight JS and exactly the same as the sequential program. 18 | * 19 | * Parameters and FlatJS types that are shared with the worker are in 20 | * ray-common.flat_js. Computation is in ray-worker.flat_js. 21 | */ 22 | 23 | function main() { 24 | const RAW_MEMORY = new SharedArrayBuffer(g_height*g_width*int32.SIZE + 65536); 25 | FlatJS.init(RAW_MEMORY, 0, RAW_MEMORY.byteLength, true); 26 | 27 | // Input: the scene graph, in shared memory 28 | const [eye, light, background, world] = setStage(); 29 | 30 | //var s = ""; 31 | //Surface.debug(world, function (x) { s += x }, 0); 32 | //console.log(s); 33 | //return; 34 | 35 | // Output: the bitmap, in shared memory 36 | const bits = Bitmap.init(@new Bitmap, g_height, g_width, DL3(152.0/256.0, 251.0/256.0, 152.0/256.0)); 37 | 38 | // Note, numWorkers is set by the .html document. 39 | const Par = new MasterPar(new Int32Array(new SharedArrayBuffer(MasterPar.NUMINTS * Int32Array.BYTES_PER_ELEMENT)), 40 | 0, numWorkers, "ray-worker.js", doneParInit); 41 | 42 | function doneParInit() { 43 | Par.broadcast(doneSetup, "setup", RAW_MEMORY); 44 | } 45 | 46 | var then, now; 47 | function doneSetup() { 48 | then = Date.now(); 49 | Par.invoke(doneTrace, "trace", [[0, g_height], [0, g_width]], eye, light, background, world, bits); 50 | } 51 | 52 | function doneTrace() { 53 | var now = Date.now(); 54 | var mycanvas = document.getElementById("mycanvas"); 55 | var cx = mycanvas.getContext('2d'); 56 | var id = cx.createImageData(g_width, g_height); 57 | id.data.set(new Uint8Array(RAW_MEMORY, Bitmap.data(bits), g_width*g_height*4)); 58 | cx.putImageData(id, 0, 0); 59 | document.getElementById("mycaption").innerHTML = "Workers=" + numWorkers + ". Time=" + (now - then) + "ms"; 60 | } 61 | } 62 | 63 | // Colors: http://kb.iu.edu/data/aetf.html 64 | 65 | const paleGreen = DL3(152.0/256.0, 251.0/256.0, 152.0/256.0); 66 | const darkGray = DL3(169.0/256.0, 169.0/256.0, 169.0/256.0); 67 | const yellow = DL3(1.0, 1.0, 0.0); 68 | const red = DL3(1.0, 0.0, 0.0); 69 | const blue = DL3(0.0, 0.0, 1.0); 70 | 71 | // Not restricted to a rectangle, actually 72 | function rectangle(world, m, v1, v2, v3, v4) { 73 | world.push(Triangle.init(@new Triangle, m, v1, v2, v3)); 74 | world.push(Triangle.init(@new Triangle, m, v1, v3, v4)); 75 | } 76 | 77 | // Vertices are for front and back faces, both counterclockwise as seen 78 | // from the outside. 79 | // Not restricted to a cube, actually. 80 | function cube(world, m, v1, v2, v3, v4, v5, v6, v7, v8) { 81 | rectangle(world, m, v1, v2, v3, v4); // front 82 | rectangle(world, m, v2, v5, v8, v3); // right 83 | rectangle(world, m, v6, v1, v4, v7); // left 84 | rectangle(world, m, v5, v5, v7, v8); // back 85 | rectangle(world, m, v4, v3, v8, v7); // top 86 | rectangle(world, m, v6, v5, v2, v1); // bottom 87 | } 88 | 89 | function setStage() { 90 | if (debug) 91 | console.log("Setstage start"); 92 | 93 | const zzz = DL3(0,0,0); 94 | 95 | const m1 = makeMaterial(DL3(0.1, 0.2, 0.2), DL3(0.3, 0.6, 0.6), 10, DL3(0.05, 0.1, 0.1), 0); 96 | const m2 = makeMaterial(DL3(0.3, 0.3, 0.2), DL3(0.6, 0.6, 0.4), 10, DL3(0.1,0.1,0.05), 0); 97 | const m3 = makeMaterial(DL3(0.1, 0, 0), DL3(0.8,0,0), 10, DL3(0.1,0,0), 0); 98 | const m4 = makeMaterial(muli(darkGray,0.4), muli(darkGray,0.3), 100, muli(darkGray,0.3), 0.5); 99 | const m5 = makeMaterial(muli(paleGreen,0.4), muli(paleGreen,0.4), 10, muli(paleGreen,0.2), 1.0); 100 | const m6 = makeMaterial(muli(yellow,0.6), zzz, 0, muli(yellow,0.4), 0); 101 | const m7 = makeMaterial(muli(red,0.6), zzz, 0, muli(red,0.4), 0); 102 | const m8 = makeMaterial(muli(blue,0.6), zzz, 0, muli(blue,0.4), 0); 103 | 104 | var world = []; 105 | 106 | world.push(Sphere.init(@new Sphere, m1, DL3(-1, 1, -9), 1)); 107 | world.push(Sphere.init(@new Sphere, m2, DL3(1.5, 1, 0), 0.75)); 108 | world.push(Triangle.init(@new Triangle, m1, DL3(-1,0,0.75), DL3(-0.75,0,0), DL3(-0.75,1.5,0))); 109 | world.push(Triangle.init(@new Triangle, m3, DL3(-2,0,0), DL3(-0.5,0,0), DL3(-0.5,2,0))); 110 | rectangle(world, m4, DL3(-5,0,5), DL3(5,0,5), DL3(5,0,-40), DL3(-5,0,-40)); 111 | cube(world, m5, DL3(1, 1.5, 1.5), DL3(1.5, 1.5, 1.25), DL3(1.5, 1.75, 1.25), DL3(1, 1.75, 1.5), 112 | DL3(1.5, 1.5, 0.5), DL3(1, 1.5, 0.75), DL3(1, 1.75, 0.75), DL3(1.5, 1.75, 0.5)); 113 | for ( var i=0 ; i < 30 ; i++ ) 114 | world.push(Sphere.init(@new Sphere, m6, DL3((-0.6+(i*0.2)), (0.075+(i*0.05)), (1.5-(i*Math.cos(i/30.0)*0.5))), 0.075)); 115 | for ( var i=0 ; i < 60 ; i++ ) 116 | world.push(Sphere.init(@new Sphere, m7, DL3((1+0.3*Math.sin(i*(3.14/16))), (0.075+(i*0.025)), (1+0.3*Math.cos(i*(3.14/16)))), 0.025)); 117 | for ( var i=0 ; i < 60 ; i++ ) 118 | world.push(Sphere.init(@new Sphere, m8, DL3((1+0.3*Math.sin(i*(3.14/16))), (0.075+((i+8)*0.025)), (1+0.3*Math.cos(i*(3.14/16)))), 0.025)); 119 | 120 | var eye = DL3(0.5, 0.75, 5); 121 | var light = DL3(g_left-1, g_top, 2); 122 | var background = DL3(25.0/256.0,25.0/256.0,112.0/256.0); 123 | 124 | if (debug) 125 | console.log("Setstage end"); 126 | 127 | // Create bounding volume hierarchy here. This reduces rendering 128 | // time by more than 50% (on first attempt). 129 | 130 | return [eye, light, background, partition(world, computeBounds(world), 0)]; 131 | } 132 | 133 | function partition(surfaces, bounds, axis) { 134 | var left=null, right=null; 135 | var { xmin, xmax, ymin, ymax, zmin, zmax } = bounds; 136 | if (surfaces.length == 1) { 137 | left = surfaces[0]; 138 | right = null; 139 | } 140 | else if (surfaces.length == 2) { 141 | left = surfaces[0]; 142 | right = surfaces[1]; 143 | } 144 | else { 145 | var mid = 0; 146 | var center; 147 | var safety = 4; 148 | for (;;) { 149 | if (!--safety) 150 | throw new Error("Unable to partition list of length " + surfaces.length); 151 | if (axis == 0) { 152 | mid = (xmax + xmin) / 2; 153 | center = (s) => Surface.center(s).x 154 | } 155 | else if (axis == 1) { 156 | mid = (ymax + ymin) / 2; 157 | center = (s) => Surface.center(s).y 158 | } 159 | else { 160 | mid = (zmax + zmin) / 2; 161 | center = (s) => Surface.center(s).z 162 | } 163 | var lobj = []; 164 | var robj = []; 165 | for ( var i=0 ; i < surfaces.length ; i++ ) { 166 | if (center(surfaces[i]) <= mid) 167 | lobj.push(surfaces[i]); 168 | else 169 | robj.push(surfaces[i]); 170 | } 171 | axis = (axis + 1) % 3; 172 | if (robj.length && lobj.length) 173 | break; 174 | } 175 | left = lobj.length == 1 ? lobj[0] : partition(lobj, computeBounds(lobj), axis); 176 | right = robj.length == 1 ? robj[0] : partition(robj, computeBounds(robj), axis); 177 | } 178 | return Volume.init(@new Volume, xmin, xmax, ymin, ymax, zmin, zmax, left, right); 179 | } 180 | 181 | function computeBounds(surfaces) { 182 | var bounds = surfaces.map((s) => Surface.bounds(s)); 183 | return { xmin: Math.min.apply(null, bounds.map((b) => b.xmin)), 184 | xmax: Math.max.apply(null, bounds.map((b) => b.xmax)), 185 | ymin: Math.min.apply(null, bounds.map((b) => b.ymin)), 186 | ymax: Math.max.apply(null, bounds.map((b) => b.ymax)), 187 | zmin: Math.min.apply(null, bounds.map((b) => b.zmin)), 188 | zmax: Math.max.apply(null, bounds.map((b) => b.zmax)) }; 189 | } 190 | 191 | -------------------------------------------------------------------------------- /demo/ray-flatjs/ray-worker.flat_js: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript -*- */ 2 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | * 7 | * Author: Lars T Hansen, lth@acm.org / lhansen@mozilla.com 8 | */ 9 | 10 | importScripts("../../util/shim.js", 11 | "../../src/message.js", 12 | "../../src/asymmetric-barrier.js", 13 | "../../src/marshaler.js", 14 | "../../src/par.js", 15 | "../../../flatjs/libflatjs.js", 16 | "ray-common.js"); 17 | 18 | const Par = new WorkerPar(); 19 | 20 | function setup(RAW_MEMORY) { 21 | FlatJS.init(RAW_MEMORY, 0, RAW_MEMORY.byteLength, false); 22 | } 23 | 24 | var g_eye; 25 | var g_background; 26 | var g_light; 27 | var g_world; 28 | var g_bits; 29 | 30 | function trace(xmin, xlim, ymin, ylim, eye, light, background, world, bits) { 31 | // Easiest to keep these in globals. 32 | g_eye = eye; 33 | g_light = light; 34 | g_background = background; 35 | g_world = world; 36 | g_bits = bits; 37 | if (g_antialias) 38 | traceWithAntialias(xmin, xlim, ymin, ylim); 39 | else 40 | traceWithoutAntialias(xmin, xlim, ymin, ylim); 41 | } 42 | 43 | function traceWithoutAntialias(ymin, ylim, xmin, xlim) { 44 | for ( var h=ymin ; h < ylim ; h++ ) { 45 | if (debug) 46 | console.log("Row " + h); 47 | for ( var w=xmin ; w < xlim ; w++ ) { 48 | var u = g_left + (g_right - g_left)*(w + 0.5)/g_width; 49 | var v = g_bottom + (g_top - g_bottom)*(h + 0.5)/g_height; 50 | var ray = DL3(u, v, -g_eye.z); 51 | var col = raycolor(g_eye, ray, 0, SENTINEL, g_reflection_depth); 52 | Bitmap.setColor(g_bits, h, w, col); 53 | } 54 | } 55 | } 56 | 57 | const random_numbers = [ 58 | 0.495,0.840,0.636,0.407,0.026,0.547,0.223,0.349,0.033,0.643,0.558,0.481,0.039, 59 | 0.175,0.169,0.606,0.638,0.364,0.709,0.814,0.206,0.346,0.812,0.603,0.969,0.888, 60 | 0.294,0.824,0.410,0.467,0.029,0.706,0.314 61 | ]; 62 | 63 | function traceWithAntialias(ymin, ylim, xmin, xlim) { 64 | var k = 0; 65 | for ( var h=ymin ; h < ylim ; h++ ) { 66 | //if (debug) 67 | // console.log("Row " + h); 68 | for ( var w=xmin ; w < xlim ; w++ ) { 69 | // Simple stratified sampling, cf Shirley&Marschner ch 13 and a fast "random" function. 70 | const n = 4; 71 | //var k = h % 32; 72 | var rand = k % 2; 73 | var c = DL3(0,0,0); 74 | k++; 75 | for ( var p=0 ; p < n ; p++ ) { 76 | for ( var q=0 ; q < n ; q++ ) { 77 | var jx = random_numbers[rand]; rand=rand+1; 78 | var jy = random_numbers[rand]; rand=rand+1; 79 | var u = g_left + (g_right - g_left)*(w + (p + jx)/n)/g_width; 80 | var v = g_bottom + (g_top - g_bottom)*(h + (q + jy)/n)/g_height; 81 | var ray = DL3(u, v, -g_eye.z); 82 | c = add(c, raycolor(g_eye, ray, 0.0, SENTINEL, g_reflection_depth)); 83 | } 84 | } 85 | Bitmap.setColor(g_bits, h,w,divi(c,n*n)); 86 | } 87 | } 88 | } 89 | 90 | // Clamping c is not necessary provided the three color components by 91 | // themselves never add up to more than 1, and shininess == 0 or shininess >= 1. 92 | // 93 | // TODO: lighting intensity is baked into the material here, but we probably want 94 | // to factor that out and somehow attenuate light with distance from the light source, 95 | // for diffuse and specular lighting. 96 | 97 | function raycolor(eye, ray, t0, t1, depth) { 98 | var tmp = Surface.intersect(g_world, eye, ray, t0, t1); 99 | var obj = tmp.obj; 100 | var dist = tmp.dist; 101 | 102 | if (obj) { 103 | const m = Surface.material.ref(obj); 104 | const p = add(eye, muli(ray, dist)); 105 | const n1 = Surface.normal(obj, p); 106 | const l1 = normalize(sub(g_light, p)); 107 | var c = Material.ambient(m); 108 | var min_obj = NULL; 109 | 110 | // Passing NULL here and testing for it in intersect() was intended as an optimization, 111 | // since any hit will do, but does not seem to have much of an effect in scenes tested 112 | // so far - maybe not enough scene detail (too few shadows). 113 | if (g_shadows) { 114 | var tmp = Surface.intersect(g_world, add(p, muli(l1, EPS)), l1, EPS, SENTINEL); 115 | min_obj = tmp.obj; 116 | } 117 | if (!min_obj) { 118 | const diffuse = Math.max(0.0, dot(n1,l1)); 119 | const v1 = normalize(neg(ray)); 120 | const h1 = normalize(add(v1, l1)); 121 | const specular = Math.pow(Math.max(0.0, dot(n1, h1)), Material.shininess(m)); 122 | c = add(c, add(mulrefi(Material.diffuse.ref(m),diffuse), mulrefi(Material.specular.ref(m),specular))); 123 | if (g_reflection) 124 | if (depth > 0.0 && Material.mirror(m) != 0.0) { 125 | const r = sub(ray, muli(n1, 2.0*dot(ray, n1))); 126 | c = add(c, muli(raycolor(add(p, muli(r,EPS)), r, EPS, SENTINEL, depth-1), Material.mirror(m))); 127 | } 128 | } 129 | return c; 130 | } 131 | return g_background; 132 | } 133 | -------------------------------------------------------------------------------- /demo/ray-flatjs/ray-worker.js: -------------------------------------------------------------------------------- 1 | // Generated from ray-worker.flat_js by fjsc 0.5; github.com/lars-t-hansen/flatjs 2 | /* -*- mode: javascript -*- */ 3 | 4 | /* This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Author: Lars T Hansen, lth@acm.org / lhansen@mozilla.com 9 | */ 10 | 11 | importScripts("../../util/shim.js", 12 | "../../src/message.js", 13 | "../../src/asymmetric-barrier.js", 14 | "../../src/marshaler.js", 15 | "../../src/par.js", 16 | "../../../flatjs/libflatjs.js", 17 | "ray-common.js"); 18 | 19 | const Par = new WorkerPar(); 20 | 21 | function setup(RAW_MEMORY) { 22 | FlatJS.init(RAW_MEMORY, 0, RAW_MEMORY.byteLength, false); 23 | } 24 | 25 | var g_eye; 26 | var g_background; 27 | var g_light; 28 | var g_world; 29 | var g_bits; 30 | 31 | function trace(xmin, xlim, ymin, ylim, eye, light, background, world, bits) { 32 | // Easiest to keep these in globals. 33 | g_eye = eye; 34 | g_light = light; 35 | g_background = background; 36 | g_world = world; 37 | g_bits = bits; 38 | if (g_antialias) 39 | traceWithAntialias(xmin, xlim, ymin, ylim); 40 | else 41 | traceWithoutAntialias(xmin, xlim, ymin, ylim); 42 | } 43 | 44 | function traceWithoutAntialias(ymin, ylim, xmin, xlim) { 45 | for ( var h=ymin ; h < ylim ; h++ ) { 46 | if (debug) 47 | console.log("Row " + h); 48 | for ( var w=xmin ; w < xlim ; w++ ) { 49 | var u = g_left + (g_right - g_left)*(w + 0.5)/g_width; 50 | var v = g_bottom + (g_top - g_bottom)*(h + 0.5)/g_height; 51 | var ray = DL3(u, v, -g_eye.z); 52 | var col = raycolor(g_eye, ray, 0, SENTINEL, g_reflection_depth); 53 | Bitmap.setColor(g_bits, h, w, col); 54 | } 55 | } 56 | } 57 | 58 | const random_numbers = [ 59 | 0.495,0.840,0.636,0.407,0.026,0.547,0.223,0.349,0.033,0.643,0.558,0.481,0.039, 60 | 0.175,0.169,0.606,0.638,0.364,0.709,0.814,0.206,0.346,0.812,0.603,0.969,0.888, 61 | 0.294,0.824,0.410,0.467,0.029,0.706,0.314 62 | ]; 63 | 64 | function traceWithAntialias(ymin, ylim, xmin, xlim) { 65 | var k = 0; 66 | for ( var h=ymin ; h < ylim ; h++ ) { 67 | //if (debug) 68 | // console.log("Row " + h); 69 | for ( var w=xmin ; w < xlim ; w++ ) { 70 | // Simple stratified sampling, cf Shirley&Marschner ch 13 and a fast "random" function. 71 | const n = 4; 72 | //var k = h % 32; 73 | var rand = k % 2; 74 | var c = DL3(0,0,0); 75 | k++; 76 | for ( var p=0 ; p < n ; p++ ) { 77 | for ( var q=0 ; q < n ; q++ ) { 78 | var jx = random_numbers[rand]; rand=rand+1; 79 | var jy = random_numbers[rand]; rand=rand+1; 80 | var u = g_left + (g_right - g_left)*(w + (p + jx)/n)/g_width; 81 | var v = g_bottom + (g_top - g_bottom)*(h + (q + jy)/n)/g_height; 82 | var ray = DL3(u, v, -g_eye.z); 83 | c = add(c, raycolor(g_eye, ray, 0.0, SENTINEL, g_reflection_depth)); 84 | } 85 | } 86 | Bitmap.setColor(g_bits, h,w,divi(c,n*n)); 87 | } 88 | } 89 | } 90 | 91 | // Clamping c is not necessary provided the three color components by 92 | // themselves never add up to more than 1, and shininess == 0 or shininess >= 1. 93 | // 94 | // TODO: lighting intensity is baked into the material here, but we probably want 95 | // to factor that out and somehow attenuate light with distance from the light source, 96 | // for diffuse and specular lighting. 97 | 98 | function raycolor(eye, ray, t0, t1, depth) { 99 | var tmp = Surface.intersect(g_world, eye, ray, t0, t1); 100 | var obj = tmp.obj; 101 | var dist = tmp.dist; 102 | 103 | if (obj) { 104 | const m = (obj + 8); 105 | const p = add(eye, muli(ray, dist)); 106 | const n1 = Surface.normal(obj, p); 107 | const l1 = normalize(sub(g_light, p)); 108 | var c = Vec3._get_impl((m + 56)); 109 | var min_obj = NULL; 110 | 111 | // Passing NULL here and testing for it in intersect() was intended as an optimization, 112 | // since any hit will do, but does not seem to have much of an effect in scenes tested 113 | // so far - maybe not enough scene detail (too few shadows). 114 | if (g_shadows) { 115 | var tmp = Surface.intersect(g_world, add(p, muli(l1, EPS)), l1, EPS, SENTINEL); 116 | min_obj = tmp.obj; 117 | } 118 | if (!min_obj) { 119 | const diffuse = Math.max(0.0, dot(n1,l1)); 120 | const v1 = normalize(neg(ray)); 121 | const h1 = normalize(add(v1, l1)); 122 | const specular = Math.pow(Math.max(0.0, dot(n1, h1)), _mem_float64[(m + 48) >> 3]); 123 | c = add(c, add(mulrefi((m + 0),diffuse), mulrefi((m + 24),specular))); 124 | if (g_reflection) 125 | if (depth > 0.0 && _mem_float64[(m + 80) >> 3] != 0.0) { 126 | const r = sub(ray, muli(n1, 2.0*dot(ray, n1))); 127 | c = add(c, muli(raycolor(add(p, muli(r,EPS)), r, EPS, SENTINEL, depth-1), _mem_float64[(m + 80) >> 3])); 128 | } 129 | } 130 | return c; 131 | } 132 | return g_background; 133 | } 134 | -------------------------------------------------------------------------------- /demo/ray-flatjs/ray.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
 
6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /demo/renderWorld/README.md: -------------------------------------------------------------------------------- 1 | This program was written by Intel for the purpose of testing PJS. I 2 | have rewritten it by removing the PJS code and reusing the same kernel 3 | function for the sequential and workers case (PJS had to have a 4 | somewhat different kernel), and done some other minor cleanup. 5 | 6 | I have added a facility for overlapping computation and display using 7 | the workers. 8 | 9 | I have also rewritten the calculation of frame rate, to allow for 10 | overlapping the display of one frame with the computation of the next 11 | (in parallel mode), and to track a window of frames, not all 12 | computation since the start. As a result, the frame rate is not 13 | compatible with the frame rate computed by the original program. (The 14 | original program computed a frame rate that did not include the time 15 | to display the frame, but excluding the display time makes it 16 | impossible to see the real speedup from overlapping computation and 17 | display.) 18 | -------------------------------------------------------------------------------- /demo/renderWorld/renderWorld-kernel.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Intel Corporation 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * - Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 24 | * THE POSSIBILITY OF SUCH DAMAGE. 25 | * 26 | */ 27 | 28 | "use strict"; 29 | 30 | var globalParams; // Initialized elsewhere 31 | 32 | // Shared kernel for renderWorld sequential and worker-parallel code. 33 | // Apart from the parameter passing and the destructuring of 34 | // globalParams, this is identical to the original sequential code. 35 | 36 | function MineKernel(ylo, ylim, xlo, xlim, result, yCos, ySin, xCos, xSin, ox, oy, oz) { 37 | var { w, h, map, texmap } = globalParams; 38 | 39 | for ( var x=xlo ; x < xlim ; x++ ) { 40 | var ___xd = (x - w / 2) / h; 41 | for ( var y=ylo ; y < ylim ; y++ ) { 42 | var __yd = (y - h / 2) / h; 43 | var __zd = 1; 44 | 45 | var ___zd = __zd * yCos + __yd * ySin; 46 | var _yd = __yd * yCos - __zd * ySin; 47 | 48 | var _xd = ___xd * xCos + ___zd * xSin; 49 | var _zd = ___zd * xCos - ___xd * xSin; 50 | 51 | var col = 0; 52 | var br = 255; 53 | var ddist = 0; 54 | 55 | var closest = 32; 56 | for ( var d = 0; d < 3; d++) { 57 | var dimLength = _xd; 58 | if (d == 1) 59 | dimLength = _yd; 60 | if (d == 2) 61 | dimLength = _zd; 62 | 63 | var ll = 1 / (dimLength < 0 ? -dimLength : dimLength); 64 | var xd = (_xd) * ll; 65 | var yd = (_yd) * ll; 66 | var zd = (_zd) * ll; 67 | 68 | var initial = ox - (ox | 0); 69 | if (d == 1) 70 | initial = oy - (oy | 0); 71 | if (d == 2) 72 | initial = oz - (oz | 0); 73 | if (dimLength > 0) 74 | initial = 1 - initial; 75 | 76 | var dist = ll * initial; 77 | 78 | var xp = ox + xd * initial; 79 | var yp = oy + yd * initial; 80 | var zp = oz + zd * initial; 81 | 82 | if (dimLength < 0) { 83 | if (d == 0) 84 | xp--; 85 | if (d == 1) 86 | yp--; 87 | if (d == 2) 88 | zp--; 89 | } 90 | 91 | while (dist < closest) { 92 | var tex = map[(zp & 63) << 12 | (yp & 63) << 6 | (xp & 63)]; 93 | 94 | if (tex > 0) { 95 | var u = ((xp + zp) * 16) & 15; 96 | var v = ((yp * 16) & 15) + 16; 97 | if (d == 1) { 98 | u = (xp * 16) & 15; 99 | v = ((zp * 16) & 15); 100 | if (yd < 0) 101 | v += 32; 102 | } 103 | 104 | var cc = texmap[u + v * 16 + tex * 256 * 3]; 105 | if (cc > 0) { 106 | col = cc; 107 | ddist = 255 - ((dist / 32 * 255) | 0); 108 | br = 255 * (255 - ((d + 2) % 3) * 50) / 255; 109 | closest = dist; 110 | } 111 | } 112 | xp += xd; 113 | yp += yd; 114 | zp += zd; 115 | dist += ll; 116 | } 117 | } 118 | 119 | var r = ((col >> 16) & 0xff) * br * ddist / (255 * 255); 120 | var g = ((col >> 8) & 0xff) * br * ddist / (255 * 255); 121 | var b = ((col) & 0xff) * br * ddist / (255 * 255); 122 | 123 | result[(x + y * w) * 4 + 0] = r; 124 | result[(x + y * w) * 4 + 1] = g; 125 | result[(x + y * w) * 4 + 2] = b; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /demo/renderWorld/renderWorld-worker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Intel Corporation 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * - Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 24 | * THE POSSIBILITY OF SUCH DAMAGE. 25 | * 26 | */ 27 | 28 | "use strict"; 29 | 30 | importScripts("../../util/shim.js", 31 | "../../src/message.js", 32 | "../../src/asymmetric-barrier.js", 33 | "../../src/marshaler.js", 34 | "../../src/par.js"); 35 | 36 | importScripts("renderWorld-kernel.js"); // The MineKernel function 37 | 38 | var Par = new WorkerPar(); 39 | 40 | // MineKernel reads globalParams. 41 | var globalParams = { w:0, h:0, map:null, texmap: null }; 42 | 43 | function Setup(w, h, map, texmap) { 44 | globalParams = { w, h, map, texmap }; 45 | } 46 | -------------------------------------------------------------------------------- /demo/renderWorld/renderWorld.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 30 | Procedural Map Rendering 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 51 | 52 | 53 |

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 |
It is useful to run this test repeatedly by reloading the page.
11 |
 
12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/test-buffer-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 Buffer type. 6 | // 2015-01-19 / lhansen@mozilla.com 7 | // 8 | // Create K workers that share a buffer with the master. 9 | // 10 | // The workers will each produce M elements with values ID+(n*K) into 11 | // the buffer and then quit, where ID is the worker ID in the range 12 | // [0,K-1]. 13 | // 14 | // The master will read the elements and make sure, in the end, that 15 | // it has received all the elements in the range [0..K*M-1]. 16 | 17 | var qIdx = 0; // Start of buffer 18 | var qSize = 10; // Number of elements in buffer 19 | var bufferIdx = qIdx+qSize; 20 | var iabSize = bufferIdx+Buffer.NUMINTS; 21 | var numWorkers = 3; 22 | var numElem = 100; // Number of elements to produce, per worker 23 | var q; 24 | 25 | var iab = new Int32Array(new SharedArrayBuffer(iabSize*Int32Array.BYTES_PER_ELEMENT)); 26 | Buffer.initialize(iab, bufferIdx); 27 | 28 | function runTest() { 29 | q = new Buffer(iab, bufferIdx, iab, qIdx, qSize); 30 | 31 | // Create numWorkers workers, share the memory with them, and wait 32 | // for them all to report back that they're running. Once they're 33 | // all up, call consumer(). 34 | 35 | var readies = 0; 36 | for ( var id=0 ; id < numWorkers ; id++ ) { 37 | var w = new Worker("test-buffer-worker.js"); 38 | w.onmessage = 39 | function (ev) { 40 | msg(String(ev.data)); 41 | if (ev.data.indexOf("ready ") == 0) { 42 | ++readies; 43 | if (readies == numWorkers) 44 | setTimeout(consumer, 0); 45 | } 46 | }; 47 | w.postMessage([iab.buffer, qIdx, qSize, bufferIdx, numElem, numWorkers, id]); 48 | } 49 | } 50 | 51 | function consumer() { 52 | msg("running: master"); 53 | 54 | // Consume data and account for the received values in a local buffer. 55 | 56 | var consumed = 0; 57 | var check = new Int32Array(numWorkers*numElem); 58 | while (consumed < numWorkers*numElem) { 59 | var elt = q.take(); 60 | check[elt]++; 61 | ++consumed; 62 | } 63 | 64 | // Check that we received one of each value. 65 | 66 | msg("Checking " + numWorkers*numElem + " elements"); 67 | for ( var i=0 ; i < numWorkers*numElem ; i++ ) 68 | if (check[i] != 1) 69 | msg("Failed at element " + i + ": " + check[i]); 70 | msg("done: master"); 71 | } 72 | -------------------------------------------------------------------------------- /test/test-buffer-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-19 / lhansen@mozilla.com 6 | 7 | importScripts("../src/lock.js", 8 | "../src/buffer.js"); 9 | 10 | var DEBUG = true; 11 | 12 | onmessage = 13 | function (ev) { 14 | var [sab, qIdx, qSize, bufferIdx, numElem, numWorkers, myID] = ev.data; 15 | var iab = new Int32Array(sab); 16 | var q = new Buffer(iab, bufferIdx, iab, qIdx, qSize); 17 | 18 | // Report back that we're running. 19 | 20 | postMessage("ready " + myID); 21 | 22 | // Produce elements and insert them into the queue. 23 | 24 | var produced = 0; 25 | while (produced < numElem) { 26 | var elt = produced*numWorkers + myID; 27 | q.put(elt); 28 | ++produced; 29 | } 30 | 31 | postMessage("done: " + myID); 32 | }; 33 | -------------------------------------------------------------------------------- /test/test-buffer.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/test-float64atomics-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 float64atomics polyfill. 6 | // 7 | // Fork off several workers that each add a large number of 1's to a 8 | // counter, atomically, and check the result. 9 | // 10 | // It is easy to see that the locking is effective: try commenting out 11 | // the spinlock loop at the beginning of float64Add, for example, and 12 | // run the test that way, it will fail. 13 | 14 | // TODO: right now this just has one phase that tests float64Add, need 15 | // to test all the other operations too. 16 | 17 | var numWorkers = 5; 18 | var nbytes = (8 + 4*(MasterBarrier.NUMINTS + Atomics.NUMF64INTS) + 7) & ~7; 19 | var sab = new SharedArrayBuffer(nbytes); 20 | var dab = new Float64Array(sab); 21 | var accIdx = 0; 22 | var iab = new Int32Array(sab); 23 | var barrierIdx = 2; 24 | var atomicIdx = barrierIdx + MasterBarrier.NUMINTS; 25 | var barrierId = 1337; 26 | var barrier = new MasterBarrier(iab, barrierIdx, barrierId, numWorkers, workersQuiescent); 27 | var state = 0; 28 | var iterations = 1000000; 29 | 30 | function runTest() { 31 | for ( var i=0 ; i < numWorkers ; i++ ) { 32 | var w = new Worker("test-float64atomics-worker.js"); 33 | MasterBarrier.addWorker(w); 34 | w.addEventListener("message", function (ev) { msg(String(ev.data)) }); 35 | w.postMessage([sab, barrierIdx, barrierId, accIdx, atomicIdx, iterations]); 36 | } 37 | } 38 | 39 | var startTime; 40 | 41 | function workersQuiescent() { 42 | if (state++ == 0) { 43 | startTime = Date.now(); 44 | barrier.release(); 45 | return; 46 | } 47 | var endTime = Date.now(); 48 | var expect = numWorkers*iterations; 49 | if (dab[accIdx] != expect) 50 | msg(`Error: got ${dab[accIdx]}, expected ${expect}`); 51 | msg(`Done in ${endTime - startTime} ms`); 52 | barrier.release(); 53 | } 54 | -------------------------------------------------------------------------------- /test/test-float64atomics-worker.js: -------------------------------------------------------------------------------- 1 | importScripts("../src/float64atomics.js", 2 | "../src/message.js", 3 | "../src/asymmetric-barrier.js"); 4 | 5 | onmessage = function (ev) { 6 | var [sab, barrierIdx, barrierId, accIdx, atomicIdx, iterations] = ev.data; 7 | var dab = new Float64Array(sab); 8 | var iab = new Int32Array(sab); 9 | var barrier = new WorkerBarrier(iab, barrierIdx, barrierId); 10 | 11 | Atomics.float64Init(iab, atomicIdx); 12 | 13 | postMessage("Worker ready"); 14 | barrier.enter(); 15 | postMessage("Worker running"); 16 | // Test add 17 | for ( var i=0 ; i < iterations ; i++ ) 18 | Atomics.float64Add(dab, accIdx, 1.0); 19 | postMessage("Worker done"); 20 | barrier.enter(); 21 | } 22 | -------------------------------------------------------------------------------- /test/test-float64atomics.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/test-lock-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 Lock and Cond types. 6 | // 7 | // Create K workers that share a single-consumer, multiple-producer 8 | // bounded buffer with the master. 9 | // 10 | // The workers will each produce M elements with values ID+(n*K) into 11 | // the buffer and then quit, where ID is the worker ID in the range 12 | // [0,K-1]. 13 | // 14 | // The master will read the elements and make sure, in the end, that 15 | // it has received all the elements in the range [0..K*M-1]. 16 | 17 | var bufIdx = 0; // Start of buffer - this must be 0, for simplicity 18 | var bufSize = 10; // Number of elements in buffer 19 | var availIdx = bufIdx+bufSize; // Number of available values 20 | var leftIdx = availIdx+1; // Left end of queue (extract) 21 | var rightIdx = leftIdx+1; // Right end of queue (insert) 22 | var lockIdx = rightIdx+1; // Lock data 23 | var nonfullIdx = lockIdx+Lock.NUMINTS; // 'Nonfull' cond data 24 | var nonemptyIdx = nonfullIdx+Cond.NUMINTS; // 'Nonempty' cond data 25 | var iabSize = nonemptyIdx+Cond.NUMINTS; 26 | var numWorkers = 3; 27 | var numElem = 100; // Number of elements to produce, per worker 28 | var lock; 29 | var nonfull; 30 | var nonempty; 31 | 32 | var iab = new Int32Array(new SharedArrayBuffer(iabSize*Int32Array.BYTES_PER_ELEMENT)); 33 | Lock.initialize(iab, lockIdx); 34 | Cond.initialize(iab, nonfullIdx); 35 | Cond.initialize(iab, nonemptyIdx); 36 | 37 | function runTest() { 38 | var readies = 0; 39 | for ( var id=0 ; id < numWorkers ; id++ ) { 40 | var w = new Worker("test-lock-worker.js"); 41 | w.onmessage = 42 | function (ev) { 43 | msg(String(ev.data)); 44 | if (ev.data.indexOf("ready ") == 0) { 45 | ++readies; 46 | if (readies == numWorkers) 47 | setTimeout(consumer, 0); 48 | } 49 | }; 50 | w.postMessage([iab.buffer, bufIdx, bufSize, availIdx, leftIdx, rightIdx, lockIdx, nonfullIdx, nonemptyIdx, numElem, numWorkers, id]); 51 | } 52 | lock = new Lock(iab, lockIdx); 53 | nonfull = new Cond(lock, nonfullIdx); 54 | nonempty = new Cond(lock, nonemptyIdx); 55 | } 56 | 57 | function consumer() { 58 | msg("running: master"); 59 | 60 | // Note this code assumes bufIdx == 0 61 | var consumed = 0; 62 | var check = new Int32Array(numWorkers*numElem); 63 | while (consumed < numWorkers*numElem) { 64 | lock.lock(); 65 | // Wait until there's a value 66 | while (iab[availIdx] == 0) 67 | nonempty.wait(); 68 | var left = iab[leftIdx]; 69 | var elt = iab[left]; 70 | iab[leftIdx] = (left+1) % bufSize; 71 | check[elt]++; 72 | // If a producer might be waiting on a slot, send a wakeup 73 | if (bufSize-(--iab[availIdx]) <= numWorkers) 74 | nonfull.wake(); 75 | lock.unlock(); 76 | ++consumed; 77 | } 78 | msg("Checking " + numWorkers*numElem + " elements"); 79 | for ( var i=0 ; i < numWorkers*numElem ; i++ ) 80 | if (check[i] != 1) 81 | msg("Failed at element " + i + ": " + check[i]); 82 | msg("done: master"); 83 | } 84 | -------------------------------------------------------------------------------- /test/test-lock-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/lock.js"); 8 | 9 | var DEBUG = true; 10 | 11 | onmessage = 12 | function (ev) { 13 | var [sab, bufIdx, bufSize, availIdx, leftIdx, rightIdx, lockIdx, nonfullIdx, nonemptyIdx, numElem, numWorkers, myID] = ev.data; 14 | var iab = new Int32Array(sab); 15 | var lock = new Lock(iab, lockIdx); 16 | var nonfull = new Cond(lock, nonfullIdx); 17 | var nonempty = new Cond(lock, nonemptyIdx); 18 | 19 | postMessage("ready " + myID); 20 | 21 | // Note this code assumes bufIdx == 0 22 | var produced = 0; 23 | var sent = []; 24 | while (produced < numElem) { 25 | var elt = produced*numWorkers + myID; 26 | lock.lock(); 27 | // Wait until there's a slot 28 | var waits = 0; 29 | while (iab[availIdx] == bufSize) { 30 | --waits; 31 | nonfull.wait(); 32 | } 33 | if (DEBUG && waits) 34 | sent.push(waits); 35 | var right = iab[rightIdx]; 36 | iab[right] = elt; 37 | iab[rightIdx] = (right+1) % bufSize; 38 | if (DEBUG) 39 | sent.push(right); 40 | // If the consumer might be waiting on a value, send a wakeup 41 | if (iab[availIdx]++ == 0) 42 | nonempty.wake(); 43 | lock.unlock(); 44 | ++produced; 45 | } 46 | 47 | // The DEBUG message contains negative numbers where a 48 | // worker had to wait, indicating the number of times 49 | // through the wait loop. Large numbers indicate high 50 | // contention. On my system I'm seeing numbers around -70 51 | // sometimes, indicating that once a worker manages to 52 | // find its groove it stays there until it's done. A more 53 | // expensive computation might change that. 54 | 55 | postMessage("done: " + myID + " " + (DEBUG ? sent.join(' ') : "")); 56 | }; 57 | -------------------------------------------------------------------------------- /test/test-lock.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/test-messageport-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 | var iterations = 100000; 6 | 7 | var w = new Worker("test-messageport-worker.js"); 8 | var channel = new MessageChannel(); 9 | var p = channel.port1; 10 | var q = channel.port2; 11 | 12 | p.onmessage = workerReady; 13 | w.postMessage([iterations, q]); 14 | 15 | function workerReady(ev) { 16 | p.onmessage = processMsg; 17 | document.getElementById("button").disabled = false; 18 | } 19 | 20 | var i; 21 | var start; 22 | 23 | function runTest() { 24 | document.getElementById("button").disabled = true; 25 | msg("Master waiting"); 26 | i = 0; 27 | start = Date.now(); 28 | p.postMessage({item:0}); 29 | } 30 | 31 | function processMsg(ev) { 32 | var c = ev.data; 33 | if (++i == iterations) { 34 | msg("Should be " + iterations + ": " + c.item); 35 | msg(Math.round(1000 * (2*iterations) / (Date.now() - start)) + " messages/s"); 36 | document.getElementById("button").disabled = false; 37 | return; 38 | } 39 | p.postMessage(c); 40 | } 41 | -------------------------------------------------------------------------------- /test/test-messageport-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 | var port; 6 | 7 | onmessage = stage1; 8 | 9 | function stage1(ev) { 10 | var iterations = ev.data[0]; // Ignored, here 11 | port = ev.data[1]; 12 | port.onmessage = stage2; 13 | port.postMessage("ready"); 14 | }; 15 | 16 | function stage2(ev) { 17 | var c = ev.data; 18 | c.item++; 19 | port.postMessage(c); 20 | } 21 | -------------------------------------------------------------------------------- /test/test-messageport.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/test-par-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 | var numWorkers = 4; 6 | var padding = 32; 7 | var height = 45; 8 | var width = 37; 9 | var arraySize = height*width; 10 | var mem = new Int32Array(new SharedArrayBuffer((2*padding + MasterPar.NUMINTS + arraySize)*Int32Array.BYTES_PER_ELEMENT)); 11 | var array = new Int32Array(new SharedArrayBuffer((mem.buffer, (padding+MasterPar.NUMINTS)*4, arraySize)*Int32Array.BYTES_PER_ELEMENT)); 12 | var aux = new Int32Array(new SharedArrayBuffer(numWorkers*Int32Array.BYTES_PER_ELEMENT)); // This needs to have a distinct SharedArrayBuffer 13 | var Par; 14 | var bias = 37; 15 | 16 | for ( var i=0 ; i < numWorkers ; i++ ) 17 | aux[i] = bias; 18 | 19 | // Set up the padding 20 | for ( var i=0 ; i < padding ; i++ ) { 21 | mem[i] = 0x0F0E0D0C; 22 | mem[mem.length-1-i] = 0x0C0D0E0F; 23 | } 24 | 25 | function runTest() { 26 | Par = new MasterPar(mem, padding, numWorkers, "test-par-worker.js", ready); 27 | } 28 | 29 | var expected = 0; 30 | var steps = 0; 31 | var maxSteps = 10; 32 | var xs = []; 33 | 34 | function ready() { 35 | // Test Par.self on the worker side; also tests eval() 36 | Par.setMessageNotUnderstood(function (x) { xs.push(x) }); 37 | Par.eval(ready2, "postMessage(Par.self);"); 38 | } 39 | 40 | function ready2() { 41 | var selfs = ""; 42 | for ( var i=0 ; i < numWorkers ; i++ ) 43 | selfs += i; 44 | if (xs.sort().join("") != selfs) 45 | throw new Error("Problem with Par.self: " + xs.join(" ")); 46 | Par.setMessageNotUnderstood(null); 47 | 48 | // Test broadcast 49 | Par.broadcast(null, "setParameters", height, width, aux); 50 | 51 | // Test eval + queueing 52 | Par.eval(null, "function fudge(x) { return x }"); 53 | 54 | // Test invoke, and on the first go-around test queueing as well 55 | doStep(); 56 | } 57 | 58 | function doStep() { 59 | msg("Step " + (steps+1)); 60 | var r1 = Math.floor(Math.random() * 100) * (steps % 2 ? -1 : 1); 61 | var r2 = Math.floor(Math.random() * 100) * (steps % 2 ? -1 : 1); 62 | Par.invoke(stepDone, "computeStep", [[0,height], [0,width]], array, r1, r2); 63 | expected += r1+r2+bias; 64 | } 65 | 66 | function stepDone() { 67 | var failures = 0; 68 | 69 | ++steps; 70 | 71 | // Check the result 72 | for ( var h=0 ; h < height ; h++ ) 73 | for ( var w=0 ; w < width ; w++ ) { 74 | var item = array[h*width+w]; 75 | if (item != expected) 76 | if (++failures < 10) 77 | msg("Step " + steps + ": Failed @ " + h + ", " + w + ": " + item); 78 | } 79 | 80 | // Check the padding 81 | for ( var i=0 ; i < padding ; i++ ) { 82 | if (mem[i] != 0x0F0E0D0C) 83 | if (++failures < 10) 84 | msg("Step " + steps + ": Padding failed @ " + i + ": " + mem[i].toString(16)); 85 | if (mem[mem.length-1-i] != 0x0C0D0E0F) 86 | if (++failures < 10) 87 | msg("Step " + steps + ": Padding failed @ " + mem.length-1-i + ": " + mem[mem.length-1-i].toString(16)); 88 | } 89 | 90 | if (failures == 0 && steps < maxSteps) 91 | doStep(); 92 | else 93 | msg("Done"); 94 | } 95 | -------------------------------------------------------------------------------- /test/test-par-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/message.js", 7 | "../src/asymmetric-barrier.js", 8 | "../src/marshaler.js", 9 | "../src/par.js"); 10 | 11 | var Par = new WorkerPar(); 12 | 13 | var height = 0; 14 | var width = 0; 15 | var aux; 16 | 17 | function setParameters(_height, _width, _aux) { 18 | height = _height; 19 | width = _width; 20 | aux = _aux; 21 | } 22 | 23 | function computeStep(hmin, hmax, wmin, wmax, mem, a, b) { 24 | var n = aux[Par.self]; 25 | for ( var h=hmin ; h < hmax ; h++ ) 26 | for ( var w=wmin ; w < wmax ; w++ ) 27 | mem[h*width+w] += fudge(a+b+n); 28 | } 29 | -------------------------------------------------------------------------------- /test/test-par.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/test-postmsg-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 | var iterations = 100000; 6 | 7 | var w = new Worker("test-postmsg-worker.js"); 8 | 9 | w.onmessage = workerReady; 10 | w.postMessage([iterations]); 11 | 12 | function workerReady(ev) { 13 | w.onmessage = processMsg; 14 | document.getElementById("button").disabled = false; 15 | } 16 | 17 | var i; 18 | var start; 19 | 20 | function runTest() { 21 | document.getElementById("button").disabled = true; 22 | msg("Master waiting"); 23 | i = 0; 24 | start = Date.now(); 25 | w.postMessage({item:0}); 26 | } 27 | 28 | function processMsg(ev) { 29 | var c = ev.data; 30 | if (++i == iterations) { 31 | msg("Should be " + iterations + ": " + c.item); 32 | msg(Math.round(1000 * (2*iterations) / (Date.now() - start)) + " messages/s"); 33 | document.getElementById("button").disabled = false; 34 | return; 35 | } 36 | w.postMessage(c); 37 | } 38 | -------------------------------------------------------------------------------- /test/test-postmsg-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 | onmessage = stage1; 6 | 7 | function stage1(ev) { 8 | var iterations = ev.data[0]; 9 | onmessage = stage2; 10 | postMessage("ready"); 11 | }; 12 | 13 | function stage2(ev) { 14 | var c = ev.data; 15 | c.item++; 16 | postMessage(c); 17 | } 18 | -------------------------------------------------------------------------------- /test/test-postmsg.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

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.

17 | 18 |

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 | --------------------------------------------------------------------------------