tla-ts 0.0.3-dev.6979965 → 0.0.3-dev.79f7ed7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/parallel-worker.cjs +2 -2
- package/dist/parallel-worker.js +2 -2
- package/dist/parallel.cjs +1 -1
- package/dist/parallel.js +2 -2
- package/package.json +1 -1
- package/src/parallel-worker.ts +95 -112
- package/src/parallel.ts +14 -19
package/dist/parallel-worker.cjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* This source code is licensed under the MIT license found in the
|
|
7
7
|
* LICENSE file in the root directory of this source tree.
|
|
8
8
|
*/
|
|
9
|
-
const e=require("./transfer.cjs"),t=
|
|
9
|
+
const e=require("./transfer.cjs"),t=async()=>typeof globalThis.postMessage==`function`?globalThis:(await import(
|
|
10
10
|
/* @vite-ignore */
|
|
11
11
|
/* webpackIgnore: true */
|
|
12
|
-
`node:worker_threads`)).parentPort,
|
|
12
|
+
`node:worker_threads`)).parentPort,n=(t,n)=>{let r=new Map(t.modules),i=e=>r.get(e),a=new Map(t.classes);e.O(e._(t.source,t.config,i));let o=e.D(),s=e.y(t.config,{source:t.source,loadModule:i,loadClass:e=>a.get(e),print:()=>{},files:{readFile:void 0,writeFile:void 0}}).environment,c=e.u(t.config,s,()=>e.v(t.source,i)),l=c?.spec.next;if(!c||l===void 0)return n.postMessage({type:`abort`});let u=t.index,d=e.T(),f=e.t(s.variables.length),p={fingerprints:t.fingerprints},m=t.requests,h=t.remaining,g=c.isSymmetric?e.r():void 0,_=/* @__PURE__ */ new Int32Array(2),v={resume:()=>{},loot:e=>{}};t.ports.forEach(e=>{e?.addEventListener(`message`,e=>v.loot(e.data)),e?.start?.()});let y={states:f(t.seed).map(d)},b=(n,r)=>{let i=Atomics.load(m,u)-1,a=Math.floor((n.end-n.origin-1)/2),o=e.n(),s=n.states.slice(n.end-a,n.end),c=a>0&&s.every(o.encode);c&&(n.end-=a);let l=c?o.finish():e.n().finish();t.ports[i]?.postMessage(l,[l.words.buffer]),Atomics.store(m,u,r),Atomics.notify(m,u)},x=e=>{Atomics.store(h,u,0),Atomics.compareExchange(m,u,0,-1)!==0&&b(e,-1)},S=e=>{Atomics.store(h,u,e.end-e.origin),Atomics.store(m,u,0)},C=n=>{for(;n.origin<n.end;n.origin++){let r=n.states[n.origin];if(!n.successors&&(Atomics.load(m,u)>0&&b(n,0),Atomics.store(h,u,n.end-n.origin-1),n.successors=e.w(l,r,s,c.spec.scope),n.generated+=n.successors.length,n.successors.length===0&&t.config.checkDeadlock))return;let i=n.successors;for(;n.index<i.length;n.index++){if(e.s(p.fingerprints))return`paused`;let t=i[n.index];if(c.findViolatedActionProperty(r,t.state))return;if(!c.satisfiesActionConstraints(r,t.state)||g&&(e.c(t.state,_),!g.insert(_[0],_[1])))continue;d(t.state);let a=c.getFingerprintState(t.state);if(e.c(a,_),e.o(p.fingerprints,_[0],_[1])){if(c.findViolatedInvariant(t.state,r))return;c.satisfiesConstraints(t.state,r)&&(n.next.push(t.state),n.fingerprintStates.push(a),n.added++)}}n.outDegrees[Math.min(n.added,31)]++,n.successors=void 0,n.index=0,n.added=0}return`done`},w=async e=>{let n=/* @__PURE__ */ new Set;for(;;){let r=t.ports.reduce((e,t,r)=>!t||n.has(r)||Atomics.load(h,r)<2?e:e===void 0||Atomics.load(h,r)>Atomics.load(h,e)?r:e,void 0);if(r===void 0)return!1;let i=Atomics.compareExchange(m,r,0,u+1);if(i===-1&&n.add(r),i>0&&Atomics.wait(m,r,i),i!==0)continue;let a=await new Promise(e=>v.loot=e);if(a.count===0)n.add(r);else return e.states=f(a).map(d),e.origin=0,e.end=e.states.length,S(e),!0}},T=e=>n.postMessage(e,e.type===`level`?[e.fingerprints.buffer]:[]),E=async t=>{let n={states:y.states,origin:0,end:y.states.length,successors:void 0,index:0,added:0,generated:0,outDegrees:Array.from({length:32},()=>0),next:[],fingerprintStates:[]};for(;;){let e=C(n);if(e===void 0)return;if(x(n),e===`paused`){let e=new Promise(e=>v.resume=e);T({type:`paused`}),await e,S(n)}else if(!await w(n))break}if(e.D()===o)return y.states=n.next,Atomics.store(h,u,y.states.length),{type:`level`,level:t,generated:n.generated,added:n.next.length,outDegrees:n.outDegrees,fingerprints:c.getStoredFingerprints(n.next,n.fingerprintStates)}},D=e=>{E(e).then(e=>T(e??{type:`abort`}),()=>T({type:`abort`}))};n.addEventListener(`message`,e=>{let t=e.data;t.type===`level`&&D(t.level),t.type===`resize`&&(p.fingerprints=t.fingerprints,v.resume())}),Atomics.store(h,u,y.states.length),T({type:`ready`})};t().then(e=>{let t=r=>{let i=r.data;if(i.type===`start`){e.removeEventListener?.(`message`,t);try{n(i,e)}catch{e.postMessage({type:`abort`})}}};e.addEventListener(`message`,t),e.start?.()});
|
package/dist/parallel-worker.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* This source code is licensed under the MIT license found in the
|
|
7
7
|
* LICENSE file in the root directory of this source tree.
|
|
8
8
|
*/
|
|
9
|
-
import{D as e,O as t,T as n,_ as r,c as i,n as a,o,r as s,s as c,t as l,u,v as d,w as f,y as p}from"./transfer.js";const m=
|
|
9
|
+
import{D as e,O as t,T as n,_ as r,c as i,n as a,o,r as s,s as c,t as l,u,v as d,w as f,y as p}from"./transfer.js";const m=async()=>typeof globalThis.postMessage==`function`?globalThis:(await import(
|
|
10
10
|
/* @vite-ignore */
|
|
11
11
|
/* webpackIgnore: true */
|
|
12
|
-
`node:worker_threads`)).parentPort,
|
|
12
|
+
`node:worker_threads`)).parentPort,h=(m,h)=>{let g=new Map(m.modules),_=e=>g.get(e),v=new Map(m.classes);t(r(m.source,m.config,_));let y=e(),b=p(m.config,{source:m.source,loadModule:_,loadClass:e=>v.get(e),print:()=>{},files:{readFile:void 0,writeFile:void 0}}).environment,x=u(m.config,b,()=>d(m.source,_)),S=x?.spec.next;if(!x||S===void 0)return h.postMessage({type:`abort`});let C=m.index,w=n(),T=l(b.variables.length),E={fingerprints:m.fingerprints},D=m.requests,O=m.remaining,k=x.isSymmetric?s():void 0,A=/* @__PURE__ */ new Int32Array(2),j={resume:()=>{},loot:e=>{}};m.ports.forEach(e=>{e?.addEventListener(`message`,e=>j.loot(e.data)),e?.start?.()});let M={states:T(m.seed).map(w)},N=(e,t)=>{let n=Atomics.load(D,C)-1,r=Math.floor((e.end-e.origin-1)/2),i=a(),o=e.states.slice(e.end-r,e.end),s=r>0&&o.every(i.encode);s&&(e.end-=r);let c=s?i.finish():a().finish();m.ports[n]?.postMessage(c,[c.words.buffer]),Atomics.store(D,C,t),Atomics.notify(D,C)},P=e=>{Atomics.store(O,C,0),Atomics.compareExchange(D,C,0,-1)!==0&&N(e,-1)},F=e=>{Atomics.store(O,C,e.end-e.origin),Atomics.store(D,C,0)},I=e=>{for(;e.origin<e.end;e.origin++){let t=e.states[e.origin];if(!e.successors&&(Atomics.load(D,C)>0&&N(e,0),Atomics.store(O,C,e.end-e.origin-1),e.successors=f(S,t,b,x.spec.scope),e.generated+=e.successors.length,e.successors.length===0&&m.config.checkDeadlock))return;let n=e.successors;for(;e.index<n.length;e.index++){if(c(E.fingerprints))return`paused`;let r=n[e.index];if(x.findViolatedActionProperty(t,r.state))return;if(!x.satisfiesActionConstraints(t,r.state)||k&&(i(r.state,A),!k.insert(A[0],A[1])))continue;w(r.state);let a=x.getFingerprintState(r.state);if(i(a,A),o(E.fingerprints,A[0],A[1])){if(x.findViolatedInvariant(r.state,t))return;x.satisfiesConstraints(r.state,t)&&(e.next.push(r.state),e.fingerprintStates.push(a),e.added++)}}e.outDegrees[Math.min(e.added,31)]++,e.successors=void 0,e.index=0,e.added=0}return`done`},L=async e=>{let t=/* @__PURE__ */ new Set;for(;;){let n=m.ports.reduce((e,n,r)=>!n||t.has(r)||Atomics.load(O,r)<2?e:e===void 0||Atomics.load(O,r)>Atomics.load(O,e)?r:e,void 0);if(n===void 0)return!1;let r=Atomics.compareExchange(D,n,0,C+1);if(r===-1&&t.add(n),r>0&&Atomics.wait(D,n,r),r!==0)continue;let i=await new Promise(e=>j.loot=e);if(i.count===0)t.add(n);else return e.states=T(i).map(w),e.origin=0,e.end=e.states.length,F(e),!0}},R=e=>h.postMessage(e,e.type===`level`?[e.fingerprints.buffer]:[]),z=async t=>{let n={states:M.states,origin:0,end:M.states.length,successors:void 0,index:0,added:0,generated:0,outDegrees:Array.from({length:32},()=>0),next:[],fingerprintStates:[]};for(;;){let e=I(n);if(e===void 0)return;if(P(n),e===`paused`){let e=new Promise(e=>j.resume=e);R({type:`paused`}),await e,F(n)}else if(!await L(n))break}if(e()===y)return M.states=n.next,Atomics.store(O,C,M.states.length),{type:`level`,level:t,generated:n.generated,added:n.next.length,outDegrees:n.outDegrees,fingerprints:x.getStoredFingerprints(n.next,n.fingerprintStates)}},B=e=>{z(e).then(e=>R(e??{type:`abort`}),()=>R({type:`abort`}))};h.addEventListener(`message`,e=>{let t=e.data;t.type===`level`&&B(t.level),t.type===`resize`&&(E.fingerprints=t.fingerprints,j.resume())}),Atomics.store(O,C,M.states.length),R({type:`ready`})};m().then(e=>{let t=n=>{let r=n.data;if(r.type===`start`){e.removeEventListener?.(`message`,t);try{h(r,e)}catch{e.postMessage({type:`abort`})}}};e.addEventListener(`message`,t),e.start?.()});
|
package/dist/parallel.cjs
CHANGED
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
const e=require("./transfer.cjs"),t=async e=>{if(typeof Worker==`function`){let t=new Worker(new URL(`./parallel-worker.js`,require("url").pathToFileURL(__filename).href),{type:`module`});return t.addEventListener(`message`,t=>e(t.data)),t.addEventListener(`error`,()=>e({type:`abort`})),t}let t=new(await(import(
|
|
10
10
|
/* @vite-ignore */
|
|
11
11
|
/* webpackIgnore: true */
|
|
12
|
-
`node:worker_threads`))).Worker(new URL(`./parallel-worker.js`,require("url").pathToFileURL(__filename).href));return t.on(`message`,e),t.on(`error`,()=>e({type:`abort`})),{postMessage:(e,n)=>t.postMessage(e,n),terminate:()=>void t.terminate()}},n=async n=>{let r=typeof n.config==`string`?e.A(n.config):n.config,i=()=>e.l({...n,config:r}),a=n.workers??1;if(a<2||typeof SharedArrayBuffer!=`function`||r.view!==void 0||r.postconditions.length>0||r.possible.length>0)return i();let o=/* @__PURE__ */ new Map,s=e=>{let t=n.loadModule?.(e);return o.set(e,t),t},c=/* @__PURE__ */ new Map,l=e=>{let t=n.loadClass?.(e);return c.set(e,t),t};e.O(e._(n.source,r,s));let u=e.D(),d=e.y(r,{source:n.source,loadModule:s,loadClass:l,print:n.onPrint??console.log,files:{readFile:n.readFile,writeFile:n.writeFile}}),f=d.environment,p=e.u(r,f,()=>e.v(n.source,s));if(e.d(d.assumptions,f)||!p||p.spec.init===void 0||p.spec.next===void 0||p.properties.liveness.length>0||e.S(f))return i();let m=e.T(),h={fingerprints:e.i()},g=/* @__PURE__ */ new Int32Array(2),_=Array.from({length:a},e.n),v={generatedStates:0,distinctStates:0,queuedStates:0,depth:0,outDegrees:Array.from({length:32},()=>0)},y=[],b=()=>({status:`success`,violation:void 0,failure:void 0,...v,fingerprintDistance:e.m(v.distinctStates,v.generatedStates,()=>{let e=new BigInt64Array(v.distinctStates);return y.reduce((t,n)=>(e.set(n,t),t+n.length),0),e})}),x=t=>{let n=[];for(let r of e.C(t,f,p.spec.scope))if(m(r),v.generatedStates++,e.c(p.getFingerprintState(r),g),e.s(h.fingerprints)&&(h.fingerprints=e.a(h.fingerprints)),e.o(h.fingerprints,g[0],g[1])){if(p.findViolatedInvariant(r)||p.findFailedInitialPredicate(r))return!1;if(p.satisfiesConstraints(r)){if(!_[n.length%a].encode(r))return!1;n.push(r),v.distinctStates++}}return y.push(BigInt64Array.from(n,e=>f.runtime.getFingerprint(e))),!0};try{if(!x(p.spec.init))return i()}catch(t){if(!(t instanceof e.P))throw t;return i()}if(e.D()!==u)return i();if(v.depth=v.generatedStates===0?0:1,v.distinctStates===0)return b();let S=[],C={count:a,resolve:e=>{}},w=e=>{S.push(e),(e.type===`abort`||S.length===C.count)&&C.resolve(S.splice(0))},T=e=>new Promise(t=>{C.count=e,C.resolve=t}),E=T(a),D=await Promise.all(Array.from({length:a},()=>t(w))),
|
|
12
|
+
`node:worker_threads`))).Worker(new URL(`./parallel-worker.js`,require("url").pathToFileURL(__filename).href));return t.on(`message`,e),t.on(`error`,()=>e({type:`abort`})),{postMessage:(e,n)=>t.postMessage(e,n),terminate:()=>void t.terminate()}},n=async n=>{let r=typeof n.config==`string`?e.A(n.config):n.config,i=()=>e.l({...n,config:r}),a=n.workers??1;if(a<2||typeof SharedArrayBuffer!=`function`||r.view!==void 0||r.postconditions.length>0||r.possible.length>0)return i();let o=/* @__PURE__ */ new Map,s=e=>{let t=n.loadModule?.(e);return o.set(e,t),t},c=/* @__PURE__ */ new Map,l=e=>{let t=n.loadClass?.(e);return c.set(e,t),t};e.O(e._(n.source,r,s));let u=e.D(),d=e.y(r,{source:n.source,loadModule:s,loadClass:l,print:n.onPrint??console.log,files:{readFile:n.readFile,writeFile:n.writeFile}}),f=d.environment,p=e.u(r,f,()=>e.v(n.source,s));if(e.d(d.assumptions,f)||!p||p.spec.init===void 0||p.spec.next===void 0||p.properties.liveness.length>0||e.S(f))return i();let m=e.T(),h={fingerprints:e.i()},g=/* @__PURE__ */ new Int32Array(2),_=Array.from({length:a},e.n),v={generatedStates:0,distinctStates:0,queuedStates:0,depth:0,outDegrees:Array.from({length:32},()=>0)},y=[],b=()=>({status:`success`,violation:void 0,failure:void 0,...v,fingerprintDistance:e.m(v.distinctStates,v.generatedStates,()=>{let e=new BigInt64Array(v.distinctStates);return y.reduce((t,n)=>(e.set(n,t),t+n.length),0),e})}),x=t=>{let n=[];for(let r of e.C(t,f,p.spec.scope))if(m(r),v.generatedStates++,e.c(p.getFingerprintState(r),g),e.s(h.fingerprints)&&(h.fingerprints=e.a(h.fingerprints)),e.o(h.fingerprints,g[0],g[1])){if(p.findViolatedInvariant(r)||p.findFailedInitialPredicate(r))return!1;if(p.satisfiesConstraints(r)){if(!_[n.length%a].encode(r))return!1;n.push(r),v.distinctStates++}}return y.push(BigInt64Array.from(n,e=>f.runtime.getFingerprint(e))),!0};try{if(!x(p.spec.init))return i()}catch(t){if(!(t instanceof e.P))throw t;return i()}if(e.D()!==u)return i();if(v.depth=v.generatedStates===0?0:1,v.distinctStates===0)return b();let S=[],C={count:a,resolve:e=>{}},w=e=>{S.push(e),(e.type===`abort`||S.length===C.count)&&C.resolve(S.splice(0))},T=e=>new Promise(t=>{C.count=e,C.resolve=t}),E=T(a),D=new Int32Array(new SharedArrayBuffer(a*4)),O=new Int32Array(new SharedArrayBuffer(a*4)),k=await Promise.all(Array.from({length:a},()=>t(w))),A=()=>k.forEach(e=>e.terminate()),j=/* @__PURE__ */ new Map,M=(e,t)=>{let n=e<t?`${e}:${t}`:`${t}:${e}`,r=j.get(n)??new MessageChannel;return j.set(n,r),e<t?r.port1:r.port2};k.forEach((e,t)=>{let i=Array.from({length:a},(e,n)=>n===t?void 0:M(t,n)),s=_[t].finish(),l={type:`start`,source:n.source,config:r,modules:[...o],classes:[...c],index:t,ports:i,seed:s,fingerprints:h.fingerprints,requests:D,remaining:O};e.postMessage(l,[...i.filter(e=>e!==void 0),s.words.buffer])});let N=e=>e.some(e=>e.type===`abort`),P=e=>k.forEach(t=>t.postMessage(e)),F=async t=>{let n=t.filter(e=>e.type===`paused`).length;if(n===0||N(t))return t;h.fingerprints=e.a(h.fingerprints);let r=T(n);return P({type:`resize`,fingerprints:h.fingerprints}),[...t.filter(e=>e.type!==`paused`),...await F(await r)]};if(N(await E))return A(),i();for(let e=1;;e++){let t=T(a);D.forEach((e,t)=>Atomics.store(D,t,0)),P({type:`level`,level:e});let n=await F(await t);if(N(n))return A(),i();let r=n.flatMap(e=>e.type===`level`?[e]:[]),o=r.reduce((e,t)=>e+t.added,0);if(v.generatedStates+=r.reduce((e,t)=>e+t.generated,0),v.distinctStates+=o,r.forEach(e=>{e.outDegrees.forEach((e,t)=>v.outDegrees[t]+=e),y.push(e.fingerprints)}),o===0)return A(),b();v.depth++}};Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return n}});
|
package/dist/parallel.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* This source code is licensed under the MIT license found in the
|
|
7
7
|
* LICENSE file in the root directory of this source tree.
|
|
8
8
|
*/
|
|
9
|
-
import{A as e,C as t,D as n,O as r,P as i,S as
|
|
9
|
+
import{A as e,C as t,D as n,O as r,P as i,S as ee,T as a,_ as o,a as s,c,d as l,i as u,l as d,m as f,n as p,o as m,s as h,u as g,v as _,y as v}from"./transfer.js";const y=async e=>{if(typeof Worker==`function`){let t=new Worker(new URL(`./parallel-worker.js`,import.meta.url),{type:`module`});return t.addEventListener(`message`,t=>e(t.data)),t.addEventListener(`error`,()=>e({type:`abort`})),t}let t=new(await(import(
|
|
10
10
|
/* @vite-ignore */
|
|
11
11
|
/* webpackIgnore: true */
|
|
12
|
-
`node:worker_threads`))).Worker(new URL(`./parallel-worker.js`,import.meta.url));return t.on(`message`,e),t.on(`error`,()=>e({type:`abort`})),{postMessage:(e,n)=>t.postMessage(e,n),terminate:()=>void t.terminate()}},
|
|
12
|
+
`node:worker_threads`))).Worker(new URL(`./parallel-worker.js`,import.meta.url));return t.on(`message`,e),t.on(`error`,()=>e({type:`abort`})),{postMessage:(e,n)=>t.postMessage(e,n),terminate:()=>void t.terminate()}},b=async b=>{let x=typeof b.config==`string`?e(b.config):b.config,S=()=>d({...b,config:x}),C=b.workers??1;if(C<2||typeof SharedArrayBuffer!=`function`||x.view!==void 0||x.postconditions.length>0||x.possible.length>0)return S();let w=/* @__PURE__ */ new Map,T=e=>{let t=b.loadModule?.(e);return w.set(e,t),t},E=/* @__PURE__ */ new Map,D=e=>{let t=b.loadClass?.(e);return E.set(e,t),t};r(o(b.source,x,T));let O=n(),k=v(x,{source:b.source,loadModule:T,loadClass:D,print:b.onPrint??console.log,files:{readFile:b.readFile,writeFile:b.writeFile}}),A=k.environment,j=g(x,A,()=>_(b.source,T));if(l(k.assumptions,A)||!j||j.spec.init===void 0||j.spec.next===void 0||j.properties.liveness.length>0||ee(A))return S();let M=a(),N={fingerprints:u()},P=/* @__PURE__ */ new Int32Array(2),F=Array.from({length:C},p),I={generatedStates:0,distinctStates:0,queuedStates:0,depth:0,outDegrees:Array.from({length:32},()=>0)},L=[],R=()=>({status:`success`,violation:void 0,failure:void 0,...I,fingerprintDistance:f(I.distinctStates,I.generatedStates,()=>{let e=new BigInt64Array(I.distinctStates);return L.reduce((t,n)=>(e.set(n,t),t+n.length),0),e})}),z=e=>{let n=[];for(let r of t(e,A,j.spec.scope))if(M(r),I.generatedStates++,c(j.getFingerprintState(r),P),h(N.fingerprints)&&(N.fingerprints=s(N.fingerprints)),m(N.fingerprints,P[0],P[1])){if(j.findViolatedInvariant(r)||j.findFailedInitialPredicate(r))return!1;if(j.satisfiesConstraints(r)){if(!F[n.length%C].encode(r))return!1;n.push(r),I.distinctStates++}}return L.push(BigInt64Array.from(n,e=>A.runtime.getFingerprint(e))),!0};try{if(!z(j.spec.init))return S()}catch(e){if(!(e instanceof i))throw e;return S()}if(n()!==O)return S();if(I.depth=I.generatedStates===0?0:1,I.distinctStates===0)return R();let B=[],V={count:C,resolve:e=>{}},H=e=>{B.push(e),(e.type===`abort`||B.length===V.count)&&V.resolve(B.splice(0))},U=e=>new Promise(t=>{V.count=e,V.resolve=t}),W=U(C),G=new Int32Array(new SharedArrayBuffer(C*4)),K=new Int32Array(new SharedArrayBuffer(C*4)),q=await Promise.all(Array.from({length:C},()=>y(H))),J=()=>q.forEach(e=>e.terminate()),Y=/* @__PURE__ */ new Map,X=(e,t)=>{let n=e<t?`${e}:${t}`:`${t}:${e}`,r=Y.get(n)??new MessageChannel;return Y.set(n,r),e<t?r.port1:r.port2};q.forEach((e,t)=>{let n=Array.from({length:C},(e,n)=>n===t?void 0:X(t,n)),r=F[t].finish(),i={type:`start`,source:b.source,config:x,modules:[...w],classes:[...E],index:t,ports:n,seed:r,fingerprints:N.fingerprints,requests:G,remaining:K};e.postMessage(i,[...n.filter(e=>e!==void 0),r.words.buffer])});let Z=e=>e.some(e=>e.type===`abort`),Q=e=>q.forEach(t=>t.postMessage(e)),$=async e=>{let t=e.filter(e=>e.type===`paused`).length;if(t===0||Z(e))return e;N.fingerprints=s(N.fingerprints);let n=U(t);return Q({type:`resize`,fingerprints:N.fingerprints}),[...e.filter(e=>e.type!==`paused`),...await $(await n)]};if(Z(await W))return J(),S();for(let e=1;;e++){let t=U(C);G.forEach((e,t)=>Atomics.store(G,t,0)),Q({type:`level`,level:e});let n=await $(await t);if(Z(n))return J(),S();let r=n.flatMap(e=>e.type===`level`?[e]:[]),i=r.reduce((e,t)=>e+t.added,0);if(I.generatedStates+=r.reduce((e,t)=>e+t.generated,0),I.distinctStates+=i,r.forEach(e=>{e.outDegrees.forEach((e,t)=>I.outDegrees[t]+=e),L.push(e.fingerprints)}),i===0)return J(),R();I.depth++}};export{b as t};
|
package/package.json
CHANGED
package/src/parallel-worker.ts
CHANGED
|
@@ -21,56 +21,21 @@ import { createStateDecoder, createStateEncoder, type EncodedStates } from "./tr
|
|
|
21
21
|
import { getRankedStringCount, setStringOrder, type Value } from "./values";
|
|
22
22
|
|
|
23
23
|
const WORKER_THREADS_MODULE = "node:worker_threads";
|
|
24
|
-
|
|
25
|
-
interface PeerMessage {
|
|
26
|
-
phase: "balance";
|
|
27
|
-
level: number;
|
|
28
|
-
from: number;
|
|
29
|
-
states: EncodedStates;
|
|
30
|
-
}
|
|
24
|
+
const LOCKED = -1;
|
|
31
25
|
|
|
32
26
|
interface Expansion {
|
|
27
|
+
states: Value[][];
|
|
33
28
|
origin: number;
|
|
29
|
+
end: number;
|
|
34
30
|
successors: Successor[] | undefined;
|
|
35
31
|
index: number;
|
|
32
|
+
added: number;
|
|
36
33
|
generated: number;
|
|
37
|
-
|
|
34
|
+
outDegrees: number[];
|
|
38
35
|
next: Value[][];
|
|
39
36
|
fingerprintStates: Value[][];
|
|
40
37
|
}
|
|
41
38
|
|
|
42
|
-
interface Transfer {
|
|
43
|
-
from: number;
|
|
44
|
-
to: number;
|
|
45
|
-
count: number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const getBalancePlan = (sizes: number[]) => {
|
|
49
|
-
const total = sizes.reduce((sum, size) => sum + size, 0);
|
|
50
|
-
const average = total / sizes.length;
|
|
51
|
-
if (Math.max(...sizes) - Math.min(...sizes) <= Math.max(64, average / 4)) return [];
|
|
52
|
-
const targets = sizes.map(
|
|
53
|
-
(_, index) => Math.floor(average) + (index < total % sizes.length ? 1 : 0),
|
|
54
|
-
);
|
|
55
|
-
const donors = sizes.flatMap((size, index) =>
|
|
56
|
-
size > targets[index] ? [{ index, amount: size - targets[index] }] : [],
|
|
57
|
-
);
|
|
58
|
-
const receivers = sizes.flatMap((size, index) =>
|
|
59
|
-
size < targets[index] ? [{ index, amount: targets[index] - size }] : [],
|
|
60
|
-
);
|
|
61
|
-
const transfers: Transfer[] = [];
|
|
62
|
-
for (const donor of donors) {
|
|
63
|
-
for (const receiver of receivers) {
|
|
64
|
-
const moved = Math.min(donor.amount, receiver.amount);
|
|
65
|
-
if (moved === 0) continue;
|
|
66
|
-
transfers.push({ from: donor.index, to: receiver.index, count: moved });
|
|
67
|
-
donor.amount -= moved;
|
|
68
|
-
receiver.amount -= moved;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return transfers;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
39
|
const getParentPort = async () => {
|
|
75
40
|
if (typeof (globalThis as { postMessage?: unknown }).postMessage === "function") {
|
|
76
41
|
return globalThis as unknown as Port;
|
|
@@ -81,33 +46,6 @@ const getParentPort = async () => {
|
|
|
81
46
|
return threads.parentPort as unknown as Port;
|
|
82
47
|
};
|
|
83
48
|
|
|
84
|
-
const createInbox = () => {
|
|
85
|
-
const messages = new Map<string, PeerMessage[]>();
|
|
86
|
-
const waiters = new Map<string, { count: number; resolve: (messages: PeerMessage[]) => void }>();
|
|
87
|
-
const getKey = (phase: PeerMessage["phase"], level: number) => `${phase}:${level}`;
|
|
88
|
-
const settle = (key: string) => {
|
|
89
|
-
const waiter = waiters.get(key);
|
|
90
|
-
const received = messages.get(key) ?? [];
|
|
91
|
-
if (!waiter || received.length < waiter.count) return;
|
|
92
|
-
waiters.delete(key);
|
|
93
|
-
messages.delete(key);
|
|
94
|
-
waiter.resolve(received);
|
|
95
|
-
};
|
|
96
|
-
return {
|
|
97
|
-
deliver: (message: PeerMessage) => {
|
|
98
|
-
const key = getKey(message.phase, message.level);
|
|
99
|
-
messages.set(key, [...(messages.get(key) ?? []), message]);
|
|
100
|
-
settle(key);
|
|
101
|
-
},
|
|
102
|
-
receive: (phase: PeerMessage["phase"], level: number, count: number) =>
|
|
103
|
-
new Promise<PeerMessage[]>((resolve) => {
|
|
104
|
-
const key = getKey(phase, level);
|
|
105
|
-
waiters.set(key, { count, resolve });
|
|
106
|
-
settle(key);
|
|
107
|
-
}),
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
|
|
111
49
|
const run = (start: StartMessage, parent: Port) => {
|
|
112
50
|
const modules = new Map(start.modules);
|
|
113
51
|
const loadModule = (name: string) => modules.get(name);
|
|
@@ -132,26 +70,55 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
132
70
|
const canonicalize = createCanonicalizer();
|
|
133
71
|
const decode = createStateDecoder(environment.variables.length);
|
|
134
72
|
const shared = { fingerprints: start.fingerprints };
|
|
73
|
+
const requests = start.requests;
|
|
74
|
+
const remaining = start.remaining;
|
|
135
75
|
// Under symmetry, a successor the worker generated before has the representative it had then,
|
|
136
76
|
// so the worker skips the search for one.
|
|
137
77
|
const generatedStates = search.isSymmetric ? createFingerprintTable() : undefined;
|
|
138
78
|
const fingerprint = new Int32Array(2);
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
peer.port.start?.();
|
|
79
|
+
const waiters = { resume: () => {}, loot: (_states: EncodedStates) => {} };
|
|
80
|
+
start.ports.forEach((port) => {
|
|
81
|
+
port?.addEventListener("message", (event) => waiters.loot(event.data as EncodedStates));
|
|
82
|
+
port?.start?.();
|
|
144
83
|
});
|
|
145
84
|
const frontier = { states: decode(start.seed).map(canonicalize) };
|
|
146
|
-
|
|
147
|
-
|
|
85
|
+
|
|
86
|
+
// A worker that runs out of states asks a peer for some by writing its index + 1 to the peer's
|
|
87
|
+
// request word. The peer sends half of the states it hasn't started, keeps expanding, and wakes
|
|
88
|
+
// the workers waiting to ask it next.
|
|
89
|
+
const answer = (expansion: Expansion, request: number) => {
|
|
90
|
+
const thief = Atomics.load(requests, self) - 1;
|
|
91
|
+
const given = Math.floor((expansion.end - expansion.origin - 1) / 2);
|
|
92
|
+
const encoder = createStateEncoder();
|
|
93
|
+
const states = expansion.states.slice(expansion.end - given, expansion.end);
|
|
94
|
+
const isEncoded = given > 0 && states.every(encoder.encode);
|
|
95
|
+
if (isEncoded) expansion.end -= given;
|
|
96
|
+
const loot = isEncoded ? encoder.finish() : createStateEncoder().finish();
|
|
97
|
+
start.ports[thief]?.postMessage(loot, [loot.words.buffer]);
|
|
98
|
+
Atomics.store(requests, self, request);
|
|
99
|
+
Atomics.notify(requests, self);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// A worker answers any request before it pauses or runs out of states, and takes no more until
|
|
103
|
+
// it resumes.
|
|
104
|
+
const lock = (expansion: Expansion) => {
|
|
105
|
+
Atomics.store(remaining, self, 0);
|
|
106
|
+
if (Atomics.compareExchange(requests, self, 0, LOCKED) === 0) return;
|
|
107
|
+
answer(expansion, LOCKED);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const unlock = (expansion: Expansion) => {
|
|
111
|
+
Atomics.store(remaining, self, expansion.end - expansion.origin);
|
|
112
|
+
Atomics.store(requests, self, 0);
|
|
113
|
+
};
|
|
148
114
|
|
|
149
115
|
// The worker pauses before a successor when the shared table needs to grow, and resumes there.
|
|
150
116
|
const expand = (expansion: Expansion) => {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const state = states[expansion.origin];
|
|
117
|
+
for (; expansion.origin < expansion.end; expansion.origin++) {
|
|
118
|
+
const state = expansion.states[expansion.origin];
|
|
154
119
|
if (!expansion.successors) {
|
|
120
|
+
if (Atomics.load(requests, self) > 0) answer(expansion, 0);
|
|
121
|
+
Atomics.store(remaining, self, expansion.end - expansion.origin - 1);
|
|
155
122
|
expansion.successors = getSuccessors(nextAction, state, environment, search.spec.scope);
|
|
156
123
|
expansion.generated += expansion.successors.length;
|
|
157
124
|
if (expansion.successors.length === 0 && start.config.checkDeadlock) return undefined;
|
|
@@ -174,69 +141,85 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
174
141
|
if (!search.satisfiesConstraints(successor.state, state)) continue;
|
|
175
142
|
expansion.next.push(successor.state);
|
|
176
143
|
expansion.fingerprintStates.push(fingerprintState);
|
|
177
|
-
expansion.
|
|
144
|
+
expansion.added++;
|
|
178
145
|
}
|
|
146
|
+
expansion.outDegrees[Math.min(expansion.added, OUT_DEGREE_BUCKETS - 1)]++;
|
|
179
147
|
expansion.successors = undefined;
|
|
180
148
|
expansion.index = 0;
|
|
149
|
+
expansion.added = 0;
|
|
181
150
|
}
|
|
182
151
|
return "done";
|
|
183
152
|
};
|
|
184
153
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
154
|
+
// Each attempt asks the peer with the most states left. While another worker's request to that
|
|
155
|
+
// peer is open, the worker waits for the peer to answer it. The worker stops once no peer that
|
|
156
|
+
// takes requests has two states left.
|
|
157
|
+
const steal = async (expansion: Expansion) => {
|
|
158
|
+
const skipped = new Set<number>();
|
|
159
|
+
for (;;) {
|
|
160
|
+
const victim = start.ports.reduce<number | undefined>((best, port, index) => {
|
|
161
|
+
if (!port || skipped.has(index) || Atomics.load(remaining, index) < 2) return best;
|
|
162
|
+
return best === undefined || Atomics.load(remaining, index) > Atomics.load(remaining, best)
|
|
163
|
+
? index
|
|
164
|
+
: best;
|
|
165
|
+
}, undefined);
|
|
166
|
+
if (victim === undefined) return false;
|
|
167
|
+
const request = Atomics.compareExchange(requests, victim, 0, self + 1);
|
|
168
|
+
if (request === LOCKED) skipped.add(victim);
|
|
169
|
+
if (request > 0) Atomics.wait(requests, victim, request);
|
|
170
|
+
if (request !== 0) continue;
|
|
171
|
+
const loot = await new Promise<EncodedStates>((resolve) => (waiters.loot = resolve));
|
|
172
|
+
if (loot.count === 0) {
|
|
173
|
+
skipped.add(victim);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
expansion.states = decode(loot).map(canonicalize);
|
|
177
|
+
expansion.origin = 0;
|
|
178
|
+
expansion.end = expansion.states.length;
|
|
179
|
+
unlock(expansion);
|
|
180
|
+
return true;
|
|
193
181
|
}
|
|
194
|
-
outgoing.forEach((encoder, index) => {
|
|
195
|
-
const states = encoder.finish();
|
|
196
|
-
send(index, { phase: "balance", level, from: self, states }, [states.words.buffer]);
|
|
197
|
-
});
|
|
198
|
-
const incoming = await inbox.receive("balance", level, peers.length);
|
|
199
|
-
incoming.forEach((message) => next.push(...decode(message.states).map(canonicalize)));
|
|
200
|
-
return true;
|
|
201
182
|
};
|
|
202
183
|
|
|
203
|
-
const waiters = { resume: () => {}, balance: (_sizes: number[]) => {} };
|
|
204
184
|
const report = (message: WorkerMessage) =>
|
|
205
185
|
parent.postMessage(message, message.type === "level" ? [message.fingerprints.buffer] : []);
|
|
206
186
|
|
|
207
187
|
const runLevel = async (level: number): Promise<LevelMessage | undefined> => {
|
|
208
188
|
const expansion: Expansion = {
|
|
189
|
+
states: frontier.states,
|
|
209
190
|
origin: 0,
|
|
191
|
+
end: frontier.states.length,
|
|
210
192
|
successors: undefined,
|
|
211
193
|
index: 0,
|
|
194
|
+
added: 0,
|
|
212
195
|
generated: 0,
|
|
213
|
-
|
|
196
|
+
outDegrees: Array.from({ length: OUT_DEGREE_BUCKETS }, () => 0),
|
|
214
197
|
next: [],
|
|
215
198
|
fingerprintStates: [],
|
|
216
199
|
};
|
|
217
|
-
for (
|
|
200
|
+
for (;;) {
|
|
201
|
+
const status = expand(expansion);
|
|
218
202
|
if (status === undefined) return undefined;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
203
|
+
lock(expansion);
|
|
204
|
+
if (status === "paused") {
|
|
205
|
+
const resumed = new Promise<void>((resolve) => (waiters.resume = resolve));
|
|
206
|
+
report({ type: "paused" });
|
|
207
|
+
await resumed;
|
|
208
|
+
unlock(expansion);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (!(await steal(expansion))) break;
|
|
222
212
|
}
|
|
223
213
|
if (getRankedStringCount() !== startupStrings) return undefined;
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const outDegrees = Array.from({ length: OUT_DEGREE_BUCKETS }, () => 0);
|
|
227
|
-
expansion.newStates.forEach((degree) => outDegrees[Math.min(degree, OUT_DEGREE_BUCKETS - 1)]++);
|
|
228
|
-
const fingerprints = search.getStoredFingerprints(next, expansion.fingerprintStates);
|
|
229
|
-
const sizes = new Promise<number[]>((resolve) => (waiters.balance = resolve));
|
|
230
|
-
report({ type: "expanded", from: self, added });
|
|
231
|
-
if (!(await balance(level, next, await sizes))) return undefined;
|
|
232
|
-
frontier.states = next;
|
|
214
|
+
frontier.states = expansion.next;
|
|
215
|
+
Atomics.store(remaining, self, frontier.states.length);
|
|
233
216
|
return {
|
|
234
217
|
type: "level",
|
|
235
218
|
level,
|
|
236
219
|
generated: expansion.generated,
|
|
237
|
-
added,
|
|
238
|
-
outDegrees,
|
|
239
|
-
fingerprints,
|
|
220
|
+
added: expansion.next.length,
|
|
221
|
+
outDegrees: expansion.outDegrees,
|
|
222
|
+
fingerprints: search.getStoredFingerprints(expansion.next, expansion.fingerprintStates),
|
|
240
223
|
};
|
|
241
224
|
};
|
|
242
225
|
|
|
@@ -253,8 +236,8 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
253
236
|
shared.fingerprints = message.fingerprints;
|
|
254
237
|
waiters.resume();
|
|
255
238
|
}
|
|
256
|
-
if (message.type === "balance") waiters.balance(message.sizes);
|
|
257
239
|
});
|
|
240
|
+
Atomics.store(remaining, self, frontier.states.length);
|
|
258
241
|
report({ type: "ready" });
|
|
259
242
|
};
|
|
260
243
|
|
package/src/parallel.ts
CHANGED
|
@@ -44,6 +44,8 @@ export interface StartMessage {
|
|
|
44
44
|
ports: (Port | undefined)[];
|
|
45
45
|
seed: EncodedStates;
|
|
46
46
|
fingerprints: SharedFingerprints;
|
|
47
|
+
requests: Int32Array;
|
|
48
|
+
remaining: Int32Array;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export interface LevelMessage {
|
|
@@ -59,13 +61,11 @@ export type WorkerMessage =
|
|
|
59
61
|
| { type: "ready" }
|
|
60
62
|
| { type: "abort" }
|
|
61
63
|
| { type: "paused" }
|
|
62
|
-
| { type: "expanded"; from: number; added: number }
|
|
63
64
|
| LevelMessage;
|
|
64
65
|
|
|
65
66
|
export type CoordinatorMessage =
|
|
66
67
|
| { type: "level"; level: number }
|
|
67
|
-
| { type: "resize"; fingerprints: SharedFingerprints }
|
|
68
|
-
| { type: "balance"; sizes: number[] };
|
|
68
|
+
| { type: "resize"; fingerprints: SharedFingerprints };
|
|
69
69
|
|
|
70
70
|
interface WorkerHandle {
|
|
71
71
|
postMessage: (message: unknown, transfer?: unknown[]) => void;
|
|
@@ -225,6 +225,8 @@ export const check = async (options: CheckOptions): Promise<CheckResult> => {
|
|
|
225
225
|
pending.resolve = resolve;
|
|
226
226
|
});
|
|
227
227
|
const ready = waitForReports(count);
|
|
228
|
+
const requests = new Int32Array(new SharedArrayBuffer(count * 4));
|
|
229
|
+
const remaining = new Int32Array(new SharedArrayBuffer(count * 4));
|
|
228
230
|
const workers = await Promise.all(Array.from({ length: count }, () => spawnWorker(onMessage)));
|
|
229
231
|
const terminate = () => workers.forEach((worker) => worker.terminate());
|
|
230
232
|
const channels = new Map<string, MessageChannel>();
|
|
@@ -249,6 +251,8 @@ export const check = async (options: CheckOptions): Promise<CheckResult> => {
|
|
|
249
251
|
ports,
|
|
250
252
|
seed,
|
|
251
253
|
fingerprints: shared.fingerprints,
|
|
254
|
+
requests,
|
|
255
|
+
remaining,
|
|
252
256
|
};
|
|
253
257
|
worker.postMessage(message, [...ports.filter((port) => port !== undefined), seed.words.buffer]);
|
|
254
258
|
});
|
|
@@ -257,34 +261,25 @@ export const check = async (options: CheckOptions): Promise<CheckResult> => {
|
|
|
257
261
|
const send = (message: CoordinatorMessage) =>
|
|
258
262
|
workers.forEach((worker) => worker.postMessage(message));
|
|
259
263
|
// Workers pause when the shared table needs to grow, and resume once it has.
|
|
260
|
-
const
|
|
264
|
+
const collectLevels = async (messages: WorkerMessage[]): Promise<WorkerMessage[]> => {
|
|
261
265
|
const paused = messages.filter((message) => message.type === "paused").length;
|
|
262
266
|
if (paused === 0 || isAborted(messages)) return messages;
|
|
263
267
|
shared.fingerprints = growSharedFingerprints(shared.fingerprints);
|
|
264
268
|
const resumed = waitForReports(paused);
|
|
265
269
|
send({ type: "resize", fingerprints: shared.fingerprints });
|
|
266
|
-
const
|
|
267
|
-
return [...
|
|
270
|
+
const reported = messages.filter((message) => message.type !== "paused");
|
|
271
|
+
return [...reported, ...(await collectLevels(await resumed))];
|
|
268
272
|
};
|
|
269
273
|
if (isAborted(await ready)) {
|
|
270
274
|
terminate();
|
|
271
275
|
return fallBack();
|
|
272
276
|
}
|
|
273
277
|
for (let level = 1; ; level++) {
|
|
274
|
-
const expanding = waitForReports(count);
|
|
275
|
-
send({ type: "level", level });
|
|
276
|
-
const expansions = await collectExpansions(await expanding);
|
|
277
|
-
if (isAborted(expansions)) {
|
|
278
|
-
terminate();
|
|
279
|
-
return fallBack();
|
|
280
|
-
}
|
|
281
|
-
const sizes = Array.from({ length: count }, () => 0);
|
|
282
|
-
expansions.forEach((message) => {
|
|
283
|
-
if (message.type === "expanded") sizes[message.from] = message.added;
|
|
284
|
-
});
|
|
285
278
|
const reported = waitForReports(count);
|
|
286
|
-
|
|
287
|
-
|
|
279
|
+
// Every worker is idle, so peers can ask any of them for states once the level starts.
|
|
280
|
+
requests.forEach((_, index) => Atomics.store(requests, index, 0));
|
|
281
|
+
send({ type: "level", level });
|
|
282
|
+
const messages = await collectLevels(await reported);
|
|
288
283
|
if (isAborted(messages)) {
|
|
289
284
|
terminate();
|
|
290
285
|
return fallBack();
|