tla-ts 0.0.1 → 0.0.3-dev.a50fc89

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.
Files changed (71) hide show
  1. package/README.md +81 -42
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +215 -30
  4. package/dist/index.d.ts +215 -30
  5. package/dist/index.js +1 -1
  6. package/dist/parallel-worker.cjs +2 -2
  7. package/dist/parallel-worker.js +2 -2
  8. package/dist/parallel.cjs +1 -1
  9. package/dist/parallel.js +2 -2
  10. package/dist/run-cli.cjs +20 -1
  11. package/dist/run-cli.js +20 -1
  12. package/dist/transfer.cjs +388 -39
  13. package/dist/transfer.js +388 -39
  14. package/package.json +6 -2
  15. package/src/ast.ts +122 -5
  16. package/src/bags.ts +161 -71
  17. package/src/builtins.ts +203 -23
  18. package/src/bundled-modules.ts +63 -32
  19. package/src/check.ts +945 -238
  20. package/src/cli-options.ts +58 -0
  21. package/src/codegen.ts +52 -18
  22. package/src/config.ts +39 -11
  23. package/src/environment.ts +485 -140
  24. package/src/errors.ts +59 -1
  25. package/src/evaluate.ts +489 -137
  26. package/src/exit-codes.ts +75 -0
  27. package/src/fingerprints.ts +2 -2
  28. package/src/index.ts +9 -4
  29. package/src/instances.ts +221 -66
  30. package/src/java-overrides.ts +241 -0
  31. package/src/json.ts +153 -72
  32. package/src/levels.ts +850 -0
  33. package/src/lexer.ts +103 -28
  34. package/src/liveness.ts +47 -91
  35. package/src/obligation-action.ts +384 -0
  36. package/src/obligation-anon.ts +575 -0
  37. package/src/obligation-deref.ts +637 -0
  38. package/src/obligation-expression.ts +1060 -0
  39. package/src/obligation-format.ts +406 -0
  40. package/src/obligation-generate.ts +1074 -0
  41. package/src/obligation-levels.ts +329 -0
  42. package/src/obligation-module.ts +1042 -0
  43. package/src/obligation-printer.ts +889 -0
  44. package/src/obligation-proof.ts +76 -0
  45. package/src/obligation-source.ts +1084 -0
  46. package/src/obligation-tuples.ts +184 -0
  47. package/src/obligations.ts +316 -0
  48. package/src/parallel-worker.ts +57 -28
  49. package/src/parallel.ts +116 -62
  50. package/src/parser.ts +958 -205
  51. package/src/pretty-print.ts +259 -0
  52. package/src/randomization.ts +24 -113
  53. package/src/resolve.ts +472 -41
  54. package/src/run-cli.ts +427 -61
  55. package/src/runtime.ts +63 -28
  56. package/src/spec-description.ts +509 -0
  57. package/src/standard-levels.ts +105 -0
  58. package/src/standard-modules.ts +223 -92
  59. package/src/subexpressions.ts +21 -35
  60. package/src/substitution-levels.ts +184 -0
  61. package/src/successors.ts +261 -46
  62. package/src/symmetry.ts +65 -21
  63. package/src/tableau.ts +43 -37
  64. package/src/tlc-ext.ts +120 -33
  65. package/src/tlc-module.ts +71 -47
  66. package/src/tlc-modules.ts +1022 -0
  67. package/src/tlc-order.ts +497 -0
  68. package/src/tlc-random.ts +411 -0
  69. package/src/tlc-summary.ts +109 -0
  70. package/src/transfer.ts +26 -5
  71. package/src/values.ts +1231 -176
@@ -1,18 +1,19 @@
1
1
  import { createCanonicalizer } from "./canonical";
2
2
  import { createSearch } from "./check";
3
3
  import { createEnvironment } from "./environment";
4
- import { createFingerprintTable, getFingerprintOwner, getStateFingerprint } from "./fingerprints";
4
+ import { createFingerprintTable, getFingerprintOwner, writeStateFingerprint } from "./fingerprints";
5
5
  import type { LevelMessage, Port, StartMessage, WorkerMessage } from "./parallel";
6
- import { parseModule } from "./parser";
7
6
  import { getSuccessors } from "./successors";
7
+ import { getTlcStringOrder, getTlcVariableOrder } from "./tlc-order";
8
+ import { OUT_DEGREE_BUCKETS } from "./tlc-summary";
8
9
  import { createStateDecoder, createStateEncoder, type EncodedStates } from "./transfer";
9
- import type { Value } from "./values";
10
+ import { getRankedStringCount, setStringOrder, type Value } from "./values";
10
11
 
11
12
  const WORKER_THREADS_MODULE = "node:worker_threads";
12
13
 
13
14
  interface Pending {
14
15
  states: Value[][];
15
- parents: Value[][];
16
+ origins: number[];
16
17
  fingerprints: number[];
17
18
  }
18
19
 
@@ -33,12 +34,12 @@ interface Transfer {
33
34
  count: number;
34
35
  }
35
36
 
36
- const getBalancePlan = (sizes: number[]): Transfer[] => {
37
+ const getBalancePlan = (sizes: number[]) => {
37
38
  const total = sizes.reduce((sum, size) => sum + size, 0);
38
39
  const average = total / sizes.length;
39
40
  if (Math.max(...sizes) - Math.min(...sizes) <= Math.max(64, average / 4)) return [];
40
41
  const targets = sizes.map(
41
- (_, index) => Math.floor(total / sizes.length) + (index < total % sizes.length ? 1 : 0),
42
+ (_, index) => Math.floor(average) + (index < total % sizes.length ? 1 : 0),
42
43
  );
43
44
  const donors = sizes.flatMap((size, index) =>
44
45
  size > targets[index] ? [{ index, amount: size - targets[index] }] : [],
@@ -59,7 +60,7 @@ const getBalancePlan = (sizes: number[]): Transfer[] => {
59
60
  return transfers;
60
61
  };
61
62
 
62
- const getParentPort = async (): Promise<Port> => {
63
+ const getParentPort = async () => {
63
64
  if (typeof (globalThis as { postMessage?: unknown }).postMessage === "function") {
64
65
  return globalThis as unknown as Port;
65
66
  }
@@ -98,19 +99,28 @@ const createInbox = () => {
98
99
 
99
100
  const run = (start: StartMessage, parent: Port) => {
100
101
  const modules = new Map(start.modules);
101
- const specification = createEnvironment(parseModule(start.source), start.config, {
102
- loadModule: (name) => modules.get(name),
102
+ const loadModule = (name: string) => modules.get(name);
103
+ const classes = new Map(start.classes);
104
+ setStringOrder(getTlcStringOrder(start.source, start.config, loadModule));
105
+ const startupStrings = getRankedStringCount();
106
+ const specification = createEnvironment(start.config, {
107
+ source: start.source,
108
+ loadModule,
109
+ loadClass: (name) => classes.get(name),
103
110
  print: () => {},
104
111
  files: { readFile: undefined, writeFile: undefined },
105
112
  });
106
113
  const environment = specification.environment;
107
- const search = createSearch(start.config, environment);
108
- if (!search) return parent.postMessage({ type: "abort" } satisfies WorkerMessage);
109
- const width = environment.variables.length;
114
+ const search = createSearch(start.config, environment, () =>
115
+ getTlcVariableOrder(start.source, loadModule),
116
+ );
117
+ const nextAction = search?.spec.next;
118
+ if (!search || nextAction === undefined)
119
+ return parent.postMessage({ type: "abort" } satisfies WorkerMessage);
110
120
  const count = start.count;
111
121
  const self = start.index;
112
122
  const canonicalize = createCanonicalizer();
113
- const decode = createStateDecoder(width);
123
+ const decode = createStateDecoder(environment.variables.length);
114
124
  const table = createFingerprintTable();
115
125
  const known = createFingerprintTable();
116
126
  const fingerprint = new Int32Array(2);
@@ -130,34 +140,40 @@ const run = (start: StartMessage, parent: Port) => {
130
140
  const expand = (states: Value[][], next: Value[][]) => {
131
141
  const pending: Pending[] = Array.from({ length: count }, () => ({
132
142
  states: [],
133
- parents: [],
143
+ origins: [],
134
144
  fingerprints: [],
135
145
  }));
136
146
  const statistics = { generated: 0 };
137
- for (const state of states) {
138
- const successors = getSuccessors(search.spec.next, state, environment);
147
+ const newStates = new Int32Array(states.length);
148
+ for (let origin = 0; origin < states.length; origin++) {
149
+ const state = states[origin];
150
+ const successors = getSuccessors(nextAction, state, environment, search.spec.scope);
139
151
  statistics.generated += successors.length;
140
152
  if (successors.length === 0 && start.config.checkDeadlock) return undefined;
141
153
  for (const successor of successors) {
142
154
  if (search.findViolatedActionProperty(state, successor.state)) return undefined;
143
155
  if (!search.satisfiesActionConstraints(state, successor.state)) continue;
144
156
  canonicalize(successor.state);
145
- getStateFingerprint(search.getFingerprintState(successor.state), fingerprint);
157
+ writeStateFingerprint(search.getFingerprintState(successor.state), fingerprint);
146
158
  const owner = getFingerprintOwner(fingerprint[1], count);
147
159
  if (owner === self) {
148
160
  if (!table.insert(fingerprint[0], fingerprint[1])) continue;
149
161
  if (search.findViolatedInvariant(successor.state, state)) return undefined;
150
- if (search.satisfiesConstraints(successor.state, state)) next.push(successor.state);
151
- else table.remove(fingerprint[0], fingerprint[1]);
162
+ if (!search.satisfiesConstraints(successor.state, state)) {
163
+ table.remove(fingerprint[0], fingerprint[1]);
164
+ continue;
165
+ }
166
+ next.push(successor.state);
167
+ newStates[origin]++;
152
168
  continue;
153
169
  }
154
170
  if (!known.insert(fingerprint[0], fingerprint[1])) continue;
155
171
  pending[owner].states.push(successor.state);
156
- pending[owner].parents.push(state);
172
+ pending[owner].origins.push(origin);
157
173
  pending[owner].fingerprints.push(fingerprint[0], fingerprint[1]);
158
174
  }
159
175
  }
160
- return { pending, generated: statistics.generated };
176
+ return { pending, generated: statistics.generated, newStates };
161
177
  };
162
178
 
163
179
  const answer = (fingerprints: Int32Array | number[]) => {
@@ -168,15 +184,16 @@ const run = (start: StartMessage, parent: Port) => {
168
184
  return bits;
169
185
  };
170
186
 
171
- const commit = (pending: Pending, bits: Uint8Array, next: Value[][]) => {
187
+ const commit = (pending: Pending, bits: Uint8Array, next: Value[][], newStates: Int32Array) => {
172
188
  const unmarks: number[] = [];
173
189
  for (let index = 0; index < bits.length; index++) {
174
190
  if (bits[index] === 0) continue;
175
191
  const state = pending.states[index];
176
- const parentState = pending.parents[index];
192
+ const parentState = frontier.states[pending.origins[index]];
177
193
  if (search.findViolatedInvariant(state, parentState)) return undefined;
178
194
  if (search.satisfiesConstraints(state, parentState)) {
179
195
  next.push(state);
196
+ newStates[pending.origins[index]]++;
180
197
  continue;
181
198
  }
182
199
  known.remove(pending.fingerprints[index * 2], pending.fingerprints[index * 2 + 1]);
@@ -214,7 +231,7 @@ const run = (start: StartMessage, parent: Port) => {
214
231
  const runLevel = async (level: number): Promise<LevelMessage | undefined> => {
215
232
  const next: Value[][] = [];
216
233
  const expanded = expand(frontier.states, next);
217
- if (!expanded) return undefined;
234
+ if (!expanded || getRankedStringCount() !== startupStrings) return undefined;
218
235
  peers.forEach((peer) => {
219
236
  const fingerprints = Int32Array.from(expanded.pending[peer.index].fingerprints);
220
237
  send(peer.index, { phase: "query", level, from: self, fingerprints }, [fingerprints.buffer]);
@@ -226,11 +243,21 @@ const run = (start: StartMessage, parent: Port) => {
226
243
  });
227
244
  const answers = await inbox.receive("answer", level, peers.length);
228
245
  for (const reply of answers) {
229
- const unmarks = commit(expanded.pending[reply.from], reply.bits ?? new Uint8Array(), next);
246
+ const unmarks = commit(
247
+ expanded.pending[reply.from],
248
+ reply.bits ?? new Uint8Array(),
249
+ next,
250
+ expanded.newStates,
251
+ );
230
252
  if (!unmarks) return undefined;
231
- send(reply.from, { phase: "sizes", level, from: self, unmarks, size: 0 }, [unmarks.buffer]);
253
+ send(reply.from, { phase: "sizes", level, from: self, unmarks }, [unmarks.buffer]);
232
254
  }
233
255
  const added = next.length;
256
+ const outDegrees = Array.from({ length: OUT_DEGREE_BUCKETS }, () => 0);
257
+ expanded.newStates.forEach((degree) => outDegrees[Math.min(degree, OUT_DEGREE_BUCKETS - 1)]++);
258
+ const fingerprints = BigInt64Array.from(next, (state) =>
259
+ environment.runtime.getFingerprint(state),
260
+ );
234
261
  const sizes = Array.from({ length: count }, () => 0);
235
262
  sizes[self] = added;
236
263
  peers.forEach((peer) =>
@@ -241,12 +268,14 @@ const run = (start: StartMessage, parent: Port) => {
241
268
  if (message.unmarks) removeAll(message.unmarks);
242
269
  else sizes[message.from] = message.size ?? 0;
243
270
  });
271
+ if (getRankedStringCount() !== startupStrings) return undefined;
244
272
  if (!(await balance(level, next, sizes))) return undefined;
245
273
  frontier.states = next;
246
- return { type: "level", level, generated: expanded.generated, added };
274
+ return { type: "level", level, generated: expanded.generated, added, outDegrees, fingerprints };
247
275
  };
248
276
 
249
- const report = (message: WorkerMessage) => parent.postMessage(message);
277
+ const report = (message: WorkerMessage) =>
278
+ parent.postMessage(message, message.type === "level" ? [message.fingerprints.buffer] : []);
250
279
  const step = (level: number) => {
251
280
  runLevel(level).then(
252
281
  (message) => report(message ?? { type: "abort" }),
package/src/parallel.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Expression } from "./ast";
2
2
  import { createCanonicalizer } from "./canonical";
3
3
  import {
4
- check,
4
+ checkSequentially,
5
5
  createSearch,
6
6
  findAssumptionViolation,
7
7
  type CheckOptions,
@@ -9,11 +9,14 @@ import {
9
9
  } from "./check";
10
10
  import { parseConfig, type ModelConfig } from "./config";
11
11
  import { createEnvironment } from "./environment";
12
- import { getChildren, isImpureBuiltin, type Environment } from "./evaluate";
13
- import { createFingerprintTable, getFingerprintOwner, getStateFingerprint } from "./fingerprints";
14
- import { parseModule } from "./parser";
12
+ import { TlaEvaluationError } from "./errors";
13
+ import { isImpure } from "./evaluate";
14
+ import { createFingerprintTable, getFingerprintOwner, writeStateFingerprint } from "./fingerprints";
15
15
  import { getInitialStates } from "./successors";
16
+ import { getTlcStringOrder, getTlcVariableOrder } from "./tlc-order";
17
+ import { getFingerprintDistance, OUT_DEGREE_BUCKETS } from "./tlc-summary";
16
18
  import { createStateEncoder, type EncodedStates } from "./transfer";
19
+ import { getRankedStringCount, setStringOrder, type Value } from "./values";
17
20
 
18
21
  const WORKER_THREADS_MODULE = "node:worker_threads";
19
22
 
@@ -29,6 +32,7 @@ export interface StartMessage {
29
32
  source: string;
30
33
  config: ModelConfig;
31
34
  modules: [string, string | undefined][];
35
+ classes: [string, Uint8Array | undefined][];
32
36
  index: number;
33
37
  count: number;
34
38
  ports: (Port | undefined)[];
@@ -41,14 +45,12 @@ export interface LevelMessage {
41
45
  level: number;
42
46
  generated: number;
43
47
  added: number;
48
+ outDegrees: number[];
49
+ fingerprints: BigInt64Array;
44
50
  }
45
51
 
46
52
  export type WorkerMessage = { type: "ready" } | { type: "abort" } | LevelMessage;
47
53
 
48
- export interface ParallelCheckOptions extends CheckOptions {
49
- workers: number;
50
- }
51
-
52
54
  interface WorkerHandle {
53
55
  postMessage: (message: unknown, transfer?: unknown[]) => void;
54
56
  terminate: () => void;
@@ -73,7 +75,7 @@ const spawnWorker = async (onMessage: (message: WorkerMessage) => void): Promise
73
75
  /* @vite-ignore */ /* webpackIgnore: true */ WORKER_THREADS_MODULE
74
76
  );
75
77
  const worker = new threads.Worker(new URL("./parallel-worker.js", import.meta.url));
76
- worker.on("message", (message: WorkerMessage) => onMessage(message));
78
+ worker.on("message", onMessage);
77
79
  worker.on("error", () => onMessage({ type: "abort" }));
78
80
  return {
79
81
  postMessage: (message, transfer) =>
@@ -82,47 +84,52 @@ const spawnWorker = async (onMessage: (message: WorkerMessage) => void): Promise
82
84
  };
83
85
  };
84
86
 
85
- const usesImpureBuiltin = (expression: Expression, environment: Environment): boolean =>
86
- ((expression.kind === "Apply" || expression.kind === "OperatorReference") &&
87
- isImpureBuiltin(expression.operator, environment)) ||
88
- (expression.kind === "Identifier" && isImpureBuiltin(expression.name, environment)) ||
89
- getChildren(expression).some((child) => usesImpureBuiltin(child, environment));
90
-
91
- const isImpure = (environment: Environment) =>
92
- [...environment.definitions.values()].some(
93
- (definition) =>
94
- usesImpureBuiltin(definition.body, environment) ||
95
- (definition.kind === "Function" &&
96
- definition.bounds.some((bound) => usesImpureBuiltin(bound.set, environment))),
97
- );
98
-
99
- export const checkInParallel = async (options: ParallelCheckOptions): Promise<CheckResult> => {
87
+ export const check = async (options: CheckOptions): Promise<CheckResult> => {
100
88
  const config = typeof options.config === "string" ? parseConfig(options.config) : options.config;
101
- const checkSequentially = () => check({ ...options, config });
89
+ const fallBack = () => checkSequentially({ ...options, config });
90
+ const count = options.workers ?? 1;
102
91
  const isSequential =
103
- options.workers < 2 ||
92
+ count < 2 ||
104
93
  config.view !== undefined ||
105
94
  config.postconditions.length > 0 ||
106
95
  config.possible.length > 0;
107
- if (isSequential) return checkSequentially();
96
+ if (isSequential) return fallBack();
108
97
  const modules = new Map<string, string | undefined>();
109
- const specification = createEnvironment(parseModule(options.source), config, {
110
- loadModule: (name) => {
111
- const source = options.loadModule?.(name);
112
- modules.set(name, source);
113
- return source;
114
- },
98
+ const loadModule = (name: string) => {
99
+ const source = options.loadModule?.(name);
100
+ modules.set(name, source);
101
+ return source;
102
+ };
103
+ const classes = new Map<string, Uint8Array | undefined>();
104
+ const loadClass = (name: string) => {
105
+ const bytes = options.loadClass?.(name);
106
+ classes.set(name, bytes);
107
+ return bytes;
108
+ };
109
+ setStringOrder(getTlcStringOrder(options.source, config, loadModule));
110
+ const startupStrings = getRankedStringCount();
111
+ const specification = createEnvironment(config, {
112
+ source: options.source,
113
+ loadModule,
114
+ loadClass,
115
115
  print: options.onPrint ?? console.log,
116
116
  files: { readFile: options.readFile, writeFile: options.writeFile },
117
117
  });
118
118
  const environment = specification.environment;
119
- if (findAssumptionViolation(specification.assumptions, environment)) return checkSequentially();
120
- const search = createSearch(config, environment);
121
- if (!search || search.properties.liveness.length > 0 || isImpure(environment)) {
122
- return checkSequentially();
119
+ const search = createSearch(config, environment, () =>
120
+ getTlcVariableOrder(options.source, loadModule),
121
+ );
122
+ if (findAssumptionViolation(specification.assumptions, environment)) return fallBack();
123
+ if (
124
+ !search ||
125
+ search.spec.init === undefined ||
126
+ search.spec.next === undefined ||
127
+ search.properties.liveness.length > 0 ||
128
+ isImpure(environment)
129
+ ) {
130
+ return fallBack();
123
131
  }
124
132
 
125
- const count = options.workers;
126
133
  const canonicalize = createCanonicalizer();
127
134
  const table = createFingerprintTable();
128
135
  const fingerprint = new Int32Array(2);
@@ -130,30 +137,70 @@ export const checkInParallel = async (options: ParallelCheckOptions): Promise<Ch
130
137
  encoder: createStateEncoder(),
131
138
  fingerprints: [] as number[],
132
139
  }));
133
- const statistics = { generatedStates: 0, distinctStates: 0, depth: 0 };
134
- for (const state of getInitialStates(search.spec.init, environment)) {
135
- canonicalize(state);
136
- statistics.generatedStates++;
137
- getStateFingerprint(search.getFingerprintState(state), fingerprint);
138
- if (!table.insert(fingerprint[0], fingerprint[1])) continue;
139
- if (search.findViolatedInvariant(state) || search.findFailedInitialPredicate(state)) {
140
- return checkSequentially();
141
- }
142
- if (!search.satisfiesConstraints(state)) {
143
- table.remove(fingerprint[0], fingerprint[1]);
144
- continue;
140
+ const statistics = {
141
+ generatedStates: 0,
142
+ distinctStates: 0,
143
+ queuedStates: 0,
144
+ depth: 0,
145
+ outDegrees: Array.from({ length: OUT_DEGREE_BUCKETS }, () => 0),
146
+ };
147
+ const tlcFingerprints: BigInt64Array[] = [];
148
+ const createSuccess = (): CheckResult => ({
149
+ status: "success",
150
+ violation: undefined,
151
+ failure: undefined,
152
+ ...statistics,
153
+ fingerprintDistance: getFingerprintDistance(
154
+ statistics.distinctStates,
155
+ statistics.generatedStates,
156
+ () => {
157
+ const fingerprints = new BigInt64Array(statistics.distinctStates);
158
+ tlcFingerprints.reduce((offset, level) => {
159
+ fingerprints.set(level, offset);
160
+ return offset + level.length;
161
+ }, 0);
162
+ return fingerprints;
163
+ },
164
+ ),
165
+ });
166
+ const seedInitialStates = (init: Expression) => {
167
+ const seeded: Value[][] = [];
168
+ for (const state of getInitialStates(init, environment, search.spec.scope)) {
169
+ canonicalize(state);
170
+ statistics.generatedStates++;
171
+ writeStateFingerprint(search.getFingerprintState(state), fingerprint);
172
+ if (!table.insert(fingerprint[0], fingerprint[1])) continue;
173
+ if (search.findViolatedInvariant(state) || search.findFailedInitialPredicate(state)) {
174
+ return false;
175
+ }
176
+ if (!search.satisfiesConstraints(state)) {
177
+ table.remove(fingerprint[0], fingerprint[1]);
178
+ continue;
179
+ }
180
+ const seed = seeds[getFingerprintOwner(fingerprint[1], count)];
181
+ if (!seed.encoder.encode(state)) return false;
182
+ seed.fingerprints.push(fingerprint[0], fingerprint[1]);
183
+ seeded.push(state);
184
+ statistics.distinctStates++;
145
185
  }
146
- const seed = seeds[getFingerprintOwner(fingerprint[1], count)];
147
- if (!seed.encoder.encode(state)) return checkSequentially();
148
- seed.fingerprints.push(fingerprint[0], fingerprint[1]);
149
- statistics.distinctStates++;
186
+ tlcFingerprints.push(
187
+ BigInt64Array.from(seeded, (state) => environment.runtime.getFingerprint(state)),
188
+ );
189
+ return true;
190
+ };
191
+ // The sequential search reports a violation or a failed evaluation in the initial states.
192
+ try {
193
+ if (!seedInitialStates(search.spec.init)) return fallBack();
194
+ } catch (error) {
195
+ if (!(error instanceof TlaEvaluationError)) throw error;
196
+ return fallBack();
150
197
  }
151
- if (statistics.distinctStates === 0)
152
- return { status: "success", violation: undefined, ...statistics };
153
- statistics.depth = 1;
198
+ if (getRankedStringCount() !== startupStrings) return fallBack();
199
+ statistics.depth = statistics.generatedStates === 0 ? 0 : 1;
200
+ if (statistics.distinctStates === 0) return createSuccess();
154
201
 
155
202
  const reports: WorkerMessage[] = [];
156
- const pending = { resolve: (_: WorkerMessage[]) => {} };
203
+ const pending = { resolve: (_reports: WorkerMessage[]) => {} };
157
204
  const onMessage = (message: WorkerMessage) => {
158
205
  reports.push(message);
159
206
  if (message.type === "abort" || reports.length === count) {
@@ -168,7 +215,7 @@ export const checkInParallel = async (options: ParallelCheckOptions): Promise<Ch
168
215
  const workers = await Promise.all(Array.from({ length: count }, () => spawnWorker(onMessage)));
169
216
  const terminate = () => workers.forEach((worker) => worker.terminate());
170
217
  const channels = new Map<string, MessageChannel>();
171
- const getPort = (from: number, to: number): Port => {
218
+ const getPort = (from: number, to: number) => {
172
219
  const key = from < to ? `${from}:${to}` : `${to}:${from}`;
173
220
  const channel = channels.get(key) ?? new MessageChannel();
174
221
  channels.set(key, channel);
@@ -185,6 +232,7 @@ export const checkInParallel = async (options: ParallelCheckOptions): Promise<Ch
185
232
  source: options.source,
186
233
  config,
187
234
  modules: [...modules],
235
+ classes: [...classes],
188
236
  index,
189
237
  count,
190
238
  ports,
@@ -201,7 +249,7 @@ export const checkInParallel = async (options: ParallelCheckOptions): Promise<Ch
201
249
  messages.some((message) => message.type === "abort");
202
250
  if (isAborted(await ready)) {
203
251
  terminate();
204
- return checkSequentially();
252
+ return fallBack();
205
253
  }
206
254
  for (let level = 1; ; level++) {
207
255
  const reported = waitForReports();
@@ -209,15 +257,21 @@ export const checkInParallel = async (options: ParallelCheckOptions): Promise<Ch
209
257
  const messages = await reported;
210
258
  if (isAborted(messages)) {
211
259
  terminate();
212
- return checkSequentially();
260
+ return fallBack();
213
261
  }
214
262
  const levels = messages.flatMap((message) => (message.type === "level" ? [message] : []));
215
263
  const added = levels.reduce((sum, message) => sum + message.added, 0);
216
264
  statistics.generatedStates += levels.reduce((sum, message) => sum + message.generated, 0);
217
265
  statistics.distinctStates += added;
266
+ levels.forEach((message) => {
267
+ message.outDegrees.forEach(
268
+ (degreeCount, degree) => (statistics.outDegrees[degree] += degreeCount),
269
+ );
270
+ tlcFingerprints.push(message.fingerprints);
271
+ });
218
272
  if (added === 0) {
219
273
  terminate();
220
- return { status: "success", violation: undefined, ...statistics };
274
+ return createSuccess();
221
275
  }
222
276
  statistics.depth++;
223
277
  }