tla-ts 0.0.1 → 0.0.3-dev.763cc71

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 +78 -30
  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,19 +1,21 @@
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[];
18
+ fingerprintStates: Value[][];
17
19
  }
18
20
 
19
21
  interface PeerMessage {
@@ -33,12 +35,12 @@ interface Transfer {
33
35
  count: number;
34
36
  }
35
37
 
36
- const getBalancePlan = (sizes: number[]): Transfer[] => {
38
+ const getBalancePlan = (sizes: number[]) => {
37
39
  const total = sizes.reduce((sum, size) => sum + size, 0);
38
40
  const average = total / sizes.length;
39
41
  if (Math.max(...sizes) - Math.min(...sizes) <= Math.max(64, average / 4)) return [];
40
42
  const targets = sizes.map(
41
- (_, index) => Math.floor(total / sizes.length) + (index < total % sizes.length ? 1 : 0),
43
+ (_, index) => Math.floor(average) + (index < total % sizes.length ? 1 : 0),
42
44
  );
43
45
  const donors = sizes.flatMap((size, index) =>
44
46
  size > targets[index] ? [{ index, amount: size - targets[index] }] : [],
@@ -59,7 +61,7 @@ const getBalancePlan = (sizes: number[]): Transfer[] => {
59
61
  return transfers;
60
62
  };
61
63
 
62
- const getParentPort = async (): Promise<Port> => {
64
+ const getParentPort = async () => {
63
65
  if (typeof (globalThis as { postMessage?: unknown }).postMessage === "function") {
64
66
  return globalThis as unknown as Port;
65
67
  }
@@ -98,21 +100,33 @@ const createInbox = () => {
98
100
 
99
101
  const run = (start: StartMessage, parent: Port) => {
100
102
  const modules = new Map(start.modules);
101
- const specification = createEnvironment(parseModule(start.source), start.config, {
102
- loadModule: (name) => modules.get(name),
103
+ const loadModule = (name: string) => modules.get(name);
104
+ const classes = new Map(start.classes);
105
+ setStringOrder(getTlcStringOrder(start.source, start.config, loadModule));
106
+ const startupStrings = getRankedStringCount();
107
+ const specification = createEnvironment(start.config, {
108
+ source: start.source,
109
+ loadModule,
110
+ loadClass: (name) => classes.get(name),
103
111
  print: () => {},
104
112
  files: { readFile: undefined, writeFile: undefined },
105
113
  });
106
114
  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;
115
+ const search = createSearch(start.config, environment, () =>
116
+ getTlcVariableOrder(start.source, loadModule),
117
+ );
118
+ const nextAction = search?.spec.next;
119
+ if (!search || nextAction === undefined)
120
+ return parent.postMessage({ type: "abort" } satisfies WorkerMessage);
110
121
  const count = start.count;
111
122
  const self = start.index;
112
123
  const canonicalize = createCanonicalizer();
113
- const decode = createStateDecoder(width);
124
+ const decode = createStateDecoder(environment.variables.length);
114
125
  const table = createFingerprintTable();
115
126
  const known = createFingerprintTable();
127
+ // Under symmetry, a successor the worker generated before has the representative it had then,
128
+ // so the worker skips the search for one.
129
+ const generatedStates = search.isSymmetric ? createFingerprintTable() : undefined;
116
130
  const fingerprint = new Int32Array(2);
117
131
  const inbox = createInbox();
118
132
  const peers = start.ports.flatMap((port, index) => (port ? [{ port, index }] : []));
@@ -127,37 +141,51 @@ const run = (start: StartMessage, parent: Port) => {
127
141
  const send = (index: number, message: PeerMessage, transfer: unknown[]) =>
128
142
  start.ports[index]?.postMessage(message, transfer);
129
143
 
130
- const expand = (states: Value[][], next: Value[][]) => {
144
+ const expand = (states: Value[][], next: Value[][], nextFingerprintStates: Value[][]) => {
131
145
  const pending: Pending[] = Array.from({ length: count }, () => ({
132
146
  states: [],
133
- parents: [],
147
+ origins: [],
134
148
  fingerprints: [],
149
+ fingerprintStates: [],
135
150
  }));
136
151
  const statistics = { generated: 0 };
137
- for (const state of states) {
138
- const successors = getSuccessors(search.spec.next, state, environment);
152
+ const newStates = new Int32Array(states.length);
153
+ for (let origin = 0; origin < states.length; origin++) {
154
+ const state = states[origin];
155
+ const successors = getSuccessors(nextAction, state, environment, search.spec.scope);
139
156
  statistics.generated += successors.length;
140
157
  if (successors.length === 0 && start.config.checkDeadlock) return undefined;
141
158
  for (const successor of successors) {
142
159
  if (search.findViolatedActionProperty(state, successor.state)) return undefined;
143
160
  if (!search.satisfiesActionConstraints(state, successor.state)) continue;
161
+ if (generatedStates) {
162
+ writeStateFingerprint(successor.state, fingerprint);
163
+ if (!generatedStates.insert(fingerprint[0], fingerprint[1])) continue;
164
+ }
144
165
  canonicalize(successor.state);
145
- getStateFingerprint(search.getFingerprintState(successor.state), fingerprint);
166
+ const fingerprintState = search.getFingerprintState(successor.state);
167
+ writeStateFingerprint(fingerprintState, fingerprint);
146
168
  const owner = getFingerprintOwner(fingerprint[1], count);
147
169
  if (owner === self) {
148
170
  if (!table.insert(fingerprint[0], fingerprint[1])) continue;
149
171
  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]);
172
+ if (!search.satisfiesConstraints(successor.state, state)) {
173
+ table.remove(fingerprint[0], fingerprint[1]);
174
+ continue;
175
+ }
176
+ next.push(successor.state);
177
+ nextFingerprintStates.push(fingerprintState);
178
+ newStates[origin]++;
152
179
  continue;
153
180
  }
154
181
  if (!known.insert(fingerprint[0], fingerprint[1])) continue;
155
182
  pending[owner].states.push(successor.state);
156
- pending[owner].parents.push(state);
183
+ pending[owner].origins.push(origin);
157
184
  pending[owner].fingerprints.push(fingerprint[0], fingerprint[1]);
185
+ pending[owner].fingerprintStates.push(fingerprintState);
158
186
  }
159
187
  }
160
- return { pending, generated: statistics.generated };
188
+ return { pending, generated: statistics.generated, newStates };
161
189
  };
162
190
 
163
191
  const answer = (fingerprints: Int32Array | number[]) => {
@@ -168,15 +196,23 @@ const run = (start: StartMessage, parent: Port) => {
168
196
  return bits;
169
197
  };
170
198
 
171
- const commit = (pending: Pending, bits: Uint8Array, next: Value[][]) => {
199
+ const commit = (
200
+ pending: Pending,
201
+ bits: Uint8Array,
202
+ next: Value[][],
203
+ nextFingerprintStates: Value[][],
204
+ newStates: Int32Array,
205
+ ) => {
172
206
  const unmarks: number[] = [];
173
207
  for (let index = 0; index < bits.length; index++) {
174
208
  if (bits[index] === 0) continue;
175
209
  const state = pending.states[index];
176
- const parentState = pending.parents[index];
210
+ const parentState = frontier.states[pending.origins[index]];
177
211
  if (search.findViolatedInvariant(state, parentState)) return undefined;
178
212
  if (search.satisfiesConstraints(state, parentState)) {
179
213
  next.push(state);
214
+ nextFingerprintStates.push(pending.fingerprintStates[index]);
215
+ newStates[pending.origins[index]]++;
180
216
  continue;
181
217
  }
182
218
  known.remove(pending.fingerprints[index * 2], pending.fingerprints[index * 2 + 1]);
@@ -213,8 +249,9 @@ const run = (start: StartMessage, parent: Port) => {
213
249
 
214
250
  const runLevel = async (level: number): Promise<LevelMessage | undefined> => {
215
251
  const next: Value[][] = [];
216
- const expanded = expand(frontier.states, next);
217
- if (!expanded) return undefined;
252
+ const nextFingerprintStates: Value[][] = [];
253
+ const expanded = expand(frontier.states, next, nextFingerprintStates);
254
+ if (!expanded || getRankedStringCount() !== startupStrings) return undefined;
218
255
  peers.forEach((peer) => {
219
256
  const fingerprints = Int32Array.from(expanded.pending[peer.index].fingerprints);
220
257
  send(peer.index, { phase: "query", level, from: self, fingerprints }, [fingerprints.buffer]);
@@ -226,11 +263,20 @@ const run = (start: StartMessage, parent: Port) => {
226
263
  });
227
264
  const answers = await inbox.receive("answer", level, peers.length);
228
265
  for (const reply of answers) {
229
- const unmarks = commit(expanded.pending[reply.from], reply.bits ?? new Uint8Array(), next);
266
+ const unmarks = commit(
267
+ expanded.pending[reply.from],
268
+ reply.bits ?? new Uint8Array(),
269
+ next,
270
+ nextFingerprintStates,
271
+ expanded.newStates,
272
+ );
230
273
  if (!unmarks) return undefined;
231
- send(reply.from, { phase: "sizes", level, from: self, unmarks, size: 0 }, [unmarks.buffer]);
274
+ send(reply.from, { phase: "sizes", level, from: self, unmarks }, [unmarks.buffer]);
232
275
  }
233
276
  const added = next.length;
277
+ const outDegrees = Array.from({ length: OUT_DEGREE_BUCKETS }, () => 0);
278
+ expanded.newStates.forEach((degree) => outDegrees[Math.min(degree, OUT_DEGREE_BUCKETS - 1)]++);
279
+ const fingerprints = search.getStoredFingerprints(next, nextFingerprintStates);
234
280
  const sizes = Array.from({ length: count }, () => 0);
235
281
  sizes[self] = added;
236
282
  peers.forEach((peer) =>
@@ -241,12 +287,14 @@ const run = (start: StartMessage, parent: Port) => {
241
287
  if (message.unmarks) removeAll(message.unmarks);
242
288
  else sizes[message.from] = message.size ?? 0;
243
289
  });
290
+ if (getRankedStringCount() !== startupStrings) return undefined;
244
291
  if (!(await balance(level, next, sizes))) return undefined;
245
292
  frontier.states = next;
246
- return { type: "level", level, generated: expanded.generated, added };
293
+ return { type: "level", level, generated: expanded.generated, added, outDegrees, fingerprints };
247
294
  };
248
295
 
249
- const report = (message: WorkerMessage) => parent.postMessage(message);
296
+ const report = (message: WorkerMessage) =>
297
+ parent.postMessage(message, message.type === "level" ? [message.fingerprints.buffer] : []);
250
298
  const step = (level: number) => {
251
299
  runLevel(level).then(
252
300
  (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
  }