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.
@@ -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=e=>{let t=e.reduce((e,t)=>e+t,0),n=t/e.length;if(Math.max(...e)-Math.min(...e)<=Math.max(64,n/4))return[];let r=e.map((r,i)=>Math.floor(n)+ +(i<t%e.length)),i=e.flatMap((e,t)=>e>r[t]?[{index:t,amount:e-r[t]}]:[]),a=e.flatMap((e,t)=>e<r[t]?[{index:t,amount:r[t]-e}]:[]),o=[];for(let e of i)for(let t of a){let n=Math.min(e.amount,t.amount);n!==0&&(o.push({from:e.index,to:t.index,count:n}),e.amount-=n,t.amount-=n)}return o},n=async()=>typeof globalThis.postMessage==`function`?globalThis:(await import(
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,r=()=>{let e=/* @__PURE__ */ new Map,t=/* @__PURE__ */ new Map,n=(e,t)=>`${e}:${t}`,r=n=>{let r=t.get(n),i=e.get(n)??[];!r||i.length<r.count||(t.delete(n),e.delete(n),r.resolve(i))};return{deliver:t=>{let i=n(t.phase,t.level);e.set(i,[...e.get(i)??[],t]),r(i)},receive:(e,i,a)=>new Promise(o=>{let s=n(e,i);t.set(s,{count:a,resolve:o}),r(s)})}},i=(n,i)=>{let a=new Map(n.modules),o=e=>a.get(e),s=new Map(n.classes);e.O(e._(n.source,n.config,o));let c=e.D(),l=e.y(n.config,{source:n.source,loadModule:o,loadClass:e=>s.get(e),print:()=>{},files:{readFile:void 0,writeFile:void 0}}).environment,u=e.u(n.config,l,()=>e.v(n.source,o)),d=u?.spec.next;if(!u||d===void 0)return i.postMessage({type:`abort`});let f=n.index,p=e.T(),m=e.t(l.variables.length),h={fingerprints:n.fingerprints},g=u.isSymmetric?e.r():void 0,_=/* @__PURE__ */ new Int32Array(2),v=r(),y=n.ports.flatMap((e,t)=>e?[{port:e,index:t}]:[]);y.forEach(e=>{e.port.addEventListener(`message`,e=>v.deliver(e.data)),e.port.start?.()});let b={states:m(n.seed).map(p)},x=(e,t,r)=>n.ports[e]?.postMessage(t,r),S=t=>{let r=b.states;for(;t.origin<r.length;t.origin++){let i=r[t.origin];if(!t.successors&&(t.successors=e.w(d,i,l,u.spec.scope),t.generated+=t.successors.length,t.successors.length===0&&n.config.checkDeadlock))return;let a=t.successors;for(;t.index<a.length;t.index++){if(e.s(h.fingerprints))return`paused`;let n=a[t.index];if(u.findViolatedActionProperty(i,n.state))return;if(!u.satisfiesActionConstraints(i,n.state)||g&&(e.c(n.state,_),!g.insert(_[0],_[1])))continue;p(n.state);let r=u.getFingerprintState(n.state);if(e.c(r,_),e.o(h.fingerprints,_[0],_[1])){if(u.findViolatedInvariant(n.state,i))return;u.satisfiesConstraints(n.state,i)&&(t.next.push(n.state),t.fingerprintStates.push(r),t.newStates[t.origin]++)}}t.successors=void 0,t.index=0}return`done`},C=async(n,r,i)=>{let a=t(i);if(a.length===0)return!0;let o=new Map(y.map(t=>[t.index,e.n()]));for(let e of a.filter(e=>e.from===f)){let t=o.get(e.to),n=r.splice(r.length-e.count,e.count);if(!t||!n.every(t.encode))return!1}return o.forEach((e,t)=>{let r=e.finish();x(t,{phase:`balance`,level:n,from:f,states:r},[r.words.buffer])}),(await v.receive(`balance`,n,y.length)).forEach(e=>r.push(...m(e.states).map(p))),!0},w={resume:()=>{},balance:e=>{}},T=e=>i.postMessage(e,e.type===`level`?[e.fingerprints.buffer]:[]),E=async t=>{let n={origin:0,successors:void 0,index:0,generated:0,newStates:new Int32Array(b.states.length),next:[],fingerprintStates:[]};for(let e=S(n);e!==`done`;e=S(n)){if(e===void 0)return;let t=new Promise(e=>w.resume=e);T({type:`paused`}),await t}if(e.D()!==c)return;let r=n.next,i=r.length,a=Array.from({length:32},()=>0);n.newStates.forEach(e=>a[Math.min(e,31)]++);let o=u.getStoredFingerprints(r,n.fingerprintStates),s=new Promise(e=>w.balance=e);if(T({type:`expanded`,from:f,added:i}),await C(t,r,await s))return b.states=r,{type:`level`,level:t,generated:n.generated,added:i,outDegrees:a,fingerprints:o}},D=e=>{E(e).then(e=>T(e??{type:`abort`}),()=>T({type:`abort`}))};i.addEventListener(`message`,e=>{let t=e.data;t.type===`level`&&D(t.level),t.type===`resize`&&(h.fingerprints=t.fingerprints,w.resume()),t.type===`balance`&&w.balance(t.sizes)}),T({type:`ready`})};n().then(e=>{let t=n=>{let r=n.data;if(r.type===`start`){e.removeEventListener?.(`message`,t);try{i(r,e)}catch{e.postMessage({type:`abort`})}}};e.addEventListener(`message`,t),e.start?.()});
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?.()});
@@ -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=e=>{let t=e.reduce((e,t)=>e+t,0),n=t/e.length;if(Math.max(...e)-Math.min(...e)<=Math.max(64,n/4))return[];let r=e.map((r,i)=>Math.floor(n)+ +(i<t%e.length)),i=e.flatMap((e,t)=>e>r[t]?[{index:t,amount:e-r[t]}]:[]),a=e.flatMap((e,t)=>e<r[t]?[{index:t,amount:r[t]-e}]:[]),o=[];for(let e of i)for(let t of a){let n=Math.min(e.amount,t.amount);n!==0&&(o.push({from:e.index,to:t.index,count:n}),e.amount-=n,t.amount-=n)}return o},h=async()=>typeof globalThis.postMessage==`function`?globalThis:(await import(
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,g=()=>{let e=/* @__PURE__ */ new Map,t=/* @__PURE__ */ new Map,n=(e,t)=>`${e}:${t}`,r=n=>{let r=t.get(n),i=e.get(n)??[];!r||i.length<r.count||(t.delete(n),e.delete(n),r.resolve(i))};return{deliver:t=>{let i=n(t.phase,t.level);e.set(i,[...e.get(i)??[],t]),r(i)},receive:(e,i,a)=>new Promise(o=>{let s=n(e,i);t.set(s,{count:a,resolve:o}),r(s)})}},_=(h,_)=>{let v=new Map(h.modules),y=e=>v.get(e),b=new Map(h.classes);t(r(h.source,h.config,y));let x=e(),S=p(h.config,{source:h.source,loadModule:y,loadClass:e=>b.get(e),print:()=>{},files:{readFile:void 0,writeFile:void 0}}).environment,C=u(h.config,S,()=>d(h.source,y)),w=C?.spec.next;if(!C||w===void 0)return _.postMessage({type:`abort`});let T=h.index,E=n(),D=l(S.variables.length),O={fingerprints:h.fingerprints},k=C.isSymmetric?s():void 0,A=/* @__PURE__ */ new Int32Array(2),j=g(),M=h.ports.flatMap((e,t)=>e?[{port:e,index:t}]:[]);M.forEach(e=>{e.port.addEventListener(`message`,e=>j.deliver(e.data)),e.port.start?.()});let N={states:D(h.seed).map(E)},P=(e,t,n)=>h.ports[e]?.postMessage(t,n),F=e=>{let t=N.states;for(;e.origin<t.length;e.origin++){let n=t[e.origin];if(!e.successors&&(e.successors=f(w,n,S,C.spec.scope),e.generated+=e.successors.length,e.successors.length===0&&h.config.checkDeadlock))return;let r=e.successors;for(;e.index<r.length;e.index++){if(c(O.fingerprints))return`paused`;let t=r[e.index];if(C.findViolatedActionProperty(n,t.state))return;if(!C.satisfiesActionConstraints(n,t.state)||k&&(i(t.state,A),!k.insert(A[0],A[1])))continue;E(t.state);let a=C.getFingerprintState(t.state);if(i(a,A),o(O.fingerprints,A[0],A[1])){if(C.findViolatedInvariant(t.state,n))return;C.satisfiesConstraints(t.state,n)&&(e.next.push(t.state),e.fingerprintStates.push(a),e.newStates[e.origin]++)}}e.successors=void 0,e.index=0}return`done`},I=async(e,t,n)=>{let r=m(n);if(r.length===0)return!0;let i=new Map(M.map(e=>[e.index,a()]));for(let e of r.filter(e=>e.from===T)){let n=i.get(e.to),r=t.splice(t.length-e.count,e.count);if(!n||!r.every(n.encode))return!1}return i.forEach((t,n)=>{let r=t.finish();P(n,{phase:`balance`,level:e,from:T,states:r},[r.words.buffer])}),(await j.receive(`balance`,e,M.length)).forEach(e=>t.push(...D(e.states).map(E))),!0},L={resume:()=>{},balance:e=>{}},R=e=>_.postMessage(e,e.type===`level`?[e.fingerprints.buffer]:[]),z=async t=>{let n={origin:0,successors:void 0,index:0,generated:0,newStates:new Int32Array(N.states.length),next:[],fingerprintStates:[]};for(let e=F(n);e!==`done`;e=F(n)){if(e===void 0)return;let t=new Promise(e=>L.resume=e);R({type:`paused`}),await t}if(e()!==x)return;let r=n.next,i=r.length,a=Array.from({length:32},()=>0);n.newStates.forEach(e=>a[Math.min(e,31)]++);let o=C.getStoredFingerprints(r,n.fingerprintStates),s=new Promise(e=>L.balance=e);if(R({type:`expanded`,from:T,added:i}),await I(t,r,await s))return N.states=r,{type:`level`,level:t,generated:n.generated,added:i,outDegrees:a,fingerprints:o}},B=e=>{z(e).then(e=>R(e??{type:`abort`}),()=>R({type:`abort`}))};_.addEventListener(`message`,e=>{let t=e.data;t.type===`level`&&B(t.level),t.type===`resize`&&(O.fingerprints=t.fingerprints,L.resume()),t.type===`balance`&&L.balance(t.sizes)}),R({type:`ready`})};h().then(e=>{let t=n=>{let r=n.data;if(r.type===`start`){e.removeEventListener?.(`message`,t);try{_(r,e)}catch{e.postMessage({type:`abort`})}}};e.addEventListener(`message`,t),e.start?.()});
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))),O=()=>D.forEach(e=>e.terminate()),k=/* @__PURE__ */ new Map,A=(e,t)=>{let n=e<t?`${e}:${t}`:`${t}:${e}`,r=k.get(n)??new MessageChannel;return k.set(n,r),e<t?r.port1:r.port2};D.forEach((e,t)=>{let i=Array.from({length:a},(e,n)=>n===t?void 0:A(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};e.postMessage(l,[...i.filter(e=>e!==void 0),s.words.buffer])});let j=e=>e.some(e=>e.type===`abort`),M=e=>D.forEach(t=>t.postMessage(e)),N=async t=>{let n=t.filter(e=>e.type===`paused`).length;if(n===0||j(t))return t;h.fingerprints=e.a(h.fingerprints);let r=T(n);return M({type:`resize`,fingerprints:h.fingerprints}),[...t.filter(e=>e.type!==`paused`),...await N(await r)]};if(j(await E))return O(),i();for(let e=1;;e++){let t=T(a);M({type:`level`,level:e});let n=await N(await t);if(j(n))return O(),i();let r=Array.from({length:a},()=>0);n.forEach(e=>{e.type===`expanded`&&(r[e.from]=e.added)});let o=T(a);M({type:`balance`,sizes:r});let s=await o;if(j(s))return O(),i();let c=s.flatMap(e=>e.type===`level`?[e]:[]),l=c.reduce((e,t)=>e+t.added,0);if(v.generatedStates+=c.reduce((e,t)=>e+t.generated,0),v.distinctStates+=l,c.forEach(e=>{e.outDegrees.forEach((e,t)=>v.outDegrees[t]+=e),y.push(e.fingerprints)}),l===0)return O(),b();v.depth++}};Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return n}});
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 a,T as o,_ as s,a as c,c as l,d as u,i as d,l as f,m as p,n as m,o as h,s as g,u as _,v,y}from"./transfer.js";const b=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(
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()}},x=async x=>{let S=typeof x.config==`string`?e(x.config):x.config,C=()=>f({...x,config:S}),w=x.workers??1;if(w<2||typeof SharedArrayBuffer!=`function`||S.view!==void 0||S.postconditions.length>0||S.possible.length>0)return C();let T=/* @__PURE__ */ new Map,E=e=>{let t=x.loadModule?.(e);return T.set(e,t),t},D=/* @__PURE__ */ new Map,O=e=>{let t=x.loadClass?.(e);return D.set(e,t),t};r(s(x.source,S,E));let k=n(),A=y(S,{source:x.source,loadModule:E,loadClass:O,print:x.onPrint??console.log,files:{readFile:x.readFile,writeFile:x.writeFile}}),j=A.environment,M=_(S,j,()=>v(x.source,E));if(u(A.assumptions,j)||!M||M.spec.init===void 0||M.spec.next===void 0||M.properties.liveness.length>0||a(j))return C();let N=o(),P={fingerprints:d()},F=/* @__PURE__ */ new Int32Array(2),I=Array.from({length:w},m),L={generatedStates:0,distinctStates:0,queuedStates:0,depth:0,outDegrees:Array.from({length:32},()=>0)},R=[],z=()=>({status:`success`,violation:void 0,failure:void 0,...L,fingerprintDistance:p(L.distinctStates,L.generatedStates,()=>{let e=new BigInt64Array(L.distinctStates);return R.reduce((t,n)=>(e.set(n,t),t+n.length),0),e})}),B=e=>{let n=[];for(let r of t(e,j,M.spec.scope))if(N(r),L.generatedStates++,l(M.getFingerprintState(r),F),g(P.fingerprints)&&(P.fingerprints=c(P.fingerprints)),h(P.fingerprints,F[0],F[1])){if(M.findViolatedInvariant(r)||M.findFailedInitialPredicate(r))return!1;if(M.satisfiesConstraints(r)){if(!I[n.length%w].encode(r))return!1;n.push(r),L.distinctStates++}}return R.push(BigInt64Array.from(n,e=>j.runtime.getFingerprint(e))),!0};try{if(!B(M.spec.init))return C()}catch(e){if(!(e instanceof i))throw e;return C()}if(n()!==k)return C();if(L.depth=L.generatedStates===0?0:1,L.distinctStates===0)return z();let V=[],H={count:w,resolve:e=>{}},U=e=>{V.push(e),(e.type===`abort`||V.length===H.count)&&H.resolve(V.splice(0))},W=e=>new Promise(t=>{H.count=e,H.resolve=t}),G=W(w),K=await Promise.all(Array.from({length:w},()=>b(U))),q=()=>K.forEach(e=>e.terminate()),J=/* @__PURE__ */ new Map,Y=(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 n=Array.from({length:w},(e,n)=>n===t?void 0:Y(t,n)),r=I[t].finish(),i={type:`start`,source:x.source,config:S,modules:[...T],classes:[...D],index:t,ports:n,seed:r,fingerprints:P.fingerprints};e.postMessage(i,[...n.filter(e=>e!==void 0),r.words.buffer])});let X=e=>e.some(e=>e.type===`abort`),Z=e=>K.forEach(t=>t.postMessage(e)),Q=async e=>{let t=e.filter(e=>e.type===`paused`).length;if(t===0||X(e))return e;P.fingerprints=c(P.fingerprints);let n=W(t);return Z({type:`resize`,fingerprints:P.fingerprints}),[...e.filter(e=>e.type!==`paused`),...await Q(await n)]};if(X(await G))return q(),C();for(let e=1;;e++){let t=W(w);Z({type:`level`,level:e});let n=await Q(await t);if(X(n))return q(),C();let r=Array.from({length:w},()=>0);n.forEach(e=>{e.type===`expanded`&&(r[e.from]=e.added)});let i=W(w);Z({type:`balance`,sizes:r});let a=await i;if(X(a))return q(),C();let o=a.flatMap(e=>e.type===`level`?[e]:[]),s=o.reduce((e,t)=>e+t.added,0);if(L.generatedStates+=o.reduce((e,t)=>e+t.generated,0),L.distinctStates+=s,o.forEach(e=>{e.outDegrees.forEach((e,t)=>L.outDegrees[t]+=e),R.push(e.fingerprints)}),s===0)return q(),z();L.depth++}};export{x as t};
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tla-ts",
3
- "version": "0.0.3-dev.6979965",
3
+ "version": "0.0.3-dev.79f7ed7",
4
4
  "description": "A TLA+ interpreter and model checker in TypeScript, 2× faster than TLC",
5
5
  "keywords": [
6
6
  "formal verification",
@@ -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
- newStates: Int32Array;
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 inbox = createInbox();
140
- const peers = start.ports.flatMap((port, index) => (port ? [{ port, index }] : []));
141
- peers.forEach((peer) => {
142
- peer.port.addEventListener("message", (event) => inbox.deliver(event.data as PeerMessage));
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
- const send = (index: number, message: PeerMessage, transfer: unknown[]) =>
147
- start.ports[index]?.postMessage(message, transfer);
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
- const states = frontier.states;
152
- for (; expansion.origin < states.length; expansion.origin++) {
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.newStates[expansion.origin]++;
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
- const balance = async (level: number, next: Value[][], sizes: number[]) => {
186
- const transfers = getBalancePlan(sizes);
187
- if (transfers.length === 0) return true;
188
- const outgoing = new Map(peers.map((peer) => [peer.index, createStateEncoder()]));
189
- for (const transfer of transfers.filter((candidate) => candidate.from === self)) {
190
- const encoder = outgoing.get(transfer.to);
191
- const states = next.splice(next.length - transfer.count, transfer.count);
192
- if (!encoder || !states.every(encoder.encode)) return false;
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
- newStates: new Int32Array(frontier.states.length),
196
+ outDegrees: Array.from({ length: OUT_DEGREE_BUCKETS }, () => 0),
214
197
  next: [],
215
198
  fingerprintStates: [],
216
199
  };
217
- for (let status = expand(expansion); status !== "done"; status = expand(expansion)) {
200
+ for (;;) {
201
+ const status = expand(expansion);
218
202
  if (status === undefined) return undefined;
219
- const resumed = new Promise<void>((resolve) => (waiters.resume = resolve));
220
- report({ type: "paused" });
221
- await resumed;
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
- const next = expansion.next;
225
- const added = next.length;
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 collectExpansions = async (messages: WorkerMessage[]): Promise<WorkerMessage[]> => {
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 expanded = messages.filter((message) => message.type !== "paused");
267
- return [...expanded, ...(await collectExpansions(await resumed))];
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
- send({ type: "balance", sizes });
287
- const messages = await reported;
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();